Krzysztof Krysiński 2 тижнів тому
батько
коміт
8c703f1257

+ 15 - 0
src/ChunkyImageLib/ChunkyImage.cs

@@ -11,6 +11,7 @@ using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -935,6 +936,20 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
         }
         }
     }
     }
 
 
+    /// <param name="customBounds">Bounds used for affected chunks, will be computed from path in O(n) if null is passed</param>
+    /// <exception cref="ObjectDisposedException">This image is disposed</exception>
+    public void EnqueueDrawPath(VectorPath path, Paintable paintable, float strokeWidth, StrokeCap strokeCap,
+        Blender blender, PaintStyle style, bool antiAliasing, RectI? customBounds = null)
+    {
+        lock (lockObject)
+        {
+            ThrowIfDisposed();
+            PathOperation operation = new(path, paintable, strokeWidth, strokeCap, blender, style, antiAliasing,
+                customBounds);
+            EnqueueOperation(operation);
+        }
+    }
+
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     public void EnqueueDrawBresenhamLine(VecI from, VecI to, Paintable paintable, BlendMode blendMode)
     public void EnqueueDrawBresenhamLine(VecI from, VecI to, Paintable paintable, BlendMode blendMode)
     {
     {

+ 34 - 2
src/ChunkyImageLib/Operations/PathOperation.cs

@@ -2,6 +2,7 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
@@ -28,6 +29,15 @@ internal class PathOperation : IMirroredDrawOperation
         bounds = floatBounds.Inflate((int)Math.Ceiling(strokeWidth) + 1);
         bounds = floatBounds.Inflate((int)Math.Ceiling(strokeWidth) + 1);
     }
     }
 
 
+    public PathOperation(VectorPath path, Color color, float strokeWidth, StrokeCap cap, Blender blender, RectI? customBounds = null)
+    {
+        this.path = new VectorPath(path);
+        paint = new() { Color = color, Style = PaintStyle.Stroke, StrokeWidth = strokeWidth, StrokeCap = cap, Blender = blender };
+
+        RectI floatBounds = customBounds ?? (RectI)(path.TightBounds).RoundOutwards();
+        bounds = floatBounds.Inflate((int)Math.Ceiling(strokeWidth) + 1);
+    }
+
     public PathOperation(VectorPath path, Paintable paintable, float strokeWidth, StrokeCap cap, BlendMode blendMode,
     public PathOperation(VectorPath path, Paintable paintable, float strokeWidth, StrokeCap cap, BlendMode blendMode,
         PaintStyle style, bool antiAliasing, RectI? customBounds = null)
         PaintStyle style, bool antiAliasing, RectI? customBounds = null)
     {
     {
@@ -39,6 +49,16 @@ internal class PathOperation : IMirroredDrawOperation
         bounds = floatBounds.Inflate((int)Math.Ceiling(strokeWidth) + 1);
         bounds = floatBounds.Inflate((int)Math.Ceiling(strokeWidth) + 1);
     }
     }
 
 
+    public PathOperation(VectorPath path, Paintable paintable, float strokeWidth, StrokeCap cap, Blender blender, PaintStyle style, bool antiAliasing, RectI? customBounds)
+    {
+        this.antiAliasing = antiAliasing;
+        this.path = new VectorPath(path);
+        paint = new() { Paintable = paintable, Style = style, StrokeWidth = strokeWidth, StrokeCap = cap, Blender = blender };
+
+        RectI floatBounds = customBounds ?? (RectI)(path.Bounds).RoundOutwards();
+        bounds = floatBounds.Inflate((int)Math.Ceiling(strokeWidth) + 1);
+    }
+
     public void DrawOnChunk(Chunk targetChunk, VecI chunkPos)
     public void DrawOnChunk(Chunk targetChunk, VecI chunkPos)
     {
     {
         paint.IsAntiAliased = antiAliasing || targetChunk.Resolution != ChunkResolution.Full;
         paint.IsAntiAliased = antiAliasing || targetChunk.Resolution != ChunkResolution.Full;
@@ -68,8 +88,20 @@ internal class PathOperation : IMirroredDrawOperation
             newBounds = (RectI)newBounds.ReflectY((double)horAxisY).Round();
             newBounds = (RectI)newBounds.ReflectY((double)horAxisY).Round();
         if (paint.Paintable != null)
         if (paint.Paintable != null)
         {
         {
-            return new PathOperation(copy, paint.Paintable, paint.StrokeWidth, paint.StrokeCap, paint.BlendMode,
-                paint.Style, antiAliasing, newBounds);
+            if( paint.Blender != null)
+            {
+                return new PathOperation(copy, paint.Paintable, paint.StrokeWidth, paint.StrokeCap, paint.Blender, paint.Style, antiAliasing, newBounds);
+            }
+            else
+            {
+                return new PathOperation(copy, paint.Paintable, paint.StrokeWidth, paint.StrokeCap, paint.BlendMode,
+                    paint.Style, antiAliasing, newBounds);
+            }
+        }
+
+        if (paint.Blender != null)
+        {
+            return new PathOperation(copy, paint.Color, paint.StrokeWidth, paint.StrokeCap, paint.Blender, newBounds);
         }
         }
 
 
         return new PathOperation(copy, paint.Color, paint.StrokeWidth, paint.StrokeCap, paint.BlendMode, newBounds);
         return new PathOperation(copy, paint.Color, paint.StrokeWidth, paint.StrokeCap, paint.BlendMode, newBounds);

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit e95271836953c7a9dbc0f3b2ee02f732f670e3a5
+Subproject commit c12cea8ab6a21a47906ef53eacbd18c0531b6078

+ 35 - 8
src/PixiEditor.ChangeableDocument/Changeables/Brushes/BrushEngine.cs

@@ -4,6 +4,7 @@ using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -320,9 +321,11 @@ public class BrushEngine : IDisposable
         var stroke = brushNode.Stroke.Value;
         var stroke = brushNode.Stroke.Value;
         bool snapToPixels = brushNode.SnapToPixels.Value;
         bool snapToPixels = brushNode.SnapToPixels.Value;
         bool canReuseStamps = brushNode.CanReuseStamps.Value;
         bool canReuseStamps = brushNode.CanReuseStamps.Value;
+        Blender? stampBlender = brushNode.UseCustomStampBlender.Value ? brushNode.LastStampBlender : null;
+        //Blender? imageBlender = brushNode.UseCustomImageBlender.Value ? brushNode.LastImageBlender : null;
 
 
         if (PaintBrush(target, autoPosition, vectorShape, rect, fitToStrokeSize, pressure, content, contentTexture,
         if (PaintBrush(target, autoPosition, vectorShape, rect, fitToStrokeSize, pressure, content, contentTexture,
-                brushNode.StampBlendMode.Value, antiAliasing, fill, stroke, snapToPixels, canReuseStamps))
+                stampBlender, brushNode.StampBlendMode.Value, antiAliasing, fill, stroke, snapToPixels, canReuseStamps))
         {
         {
             lastPos = point;
             lastPos = point;
         }
         }
@@ -330,7 +333,7 @@ public class BrushEngine : IDisposable
 
 
     public bool PaintBrush(ChunkyImage target, bool autoPosition, ShapeVectorData vectorShape,
     public bool PaintBrush(ChunkyImage target, bool autoPosition, ShapeVectorData vectorShape,
         RectD rect, bool fitToStrokeSize, float pressure, Painter? content,
         RectD rect, bool fitToStrokeSize, float pressure, Painter? content,
-        Texture? contentTexture, DrawingApiBlendMode blendMode, bool antiAliasing, Paintable fill, Paintable stroke,
+        Texture? contentTexture, Blender? blender, DrawingApiBlendMode blendMode, bool antiAliasing, Paintable fill, Paintable stroke,
         bool snapToPixels, bool canReuseStamps)
         bool snapToPixels, bool canReuseStamps)
     {
     {
         var path = vectorShape.ToPath(true);
         var path = vectorShape.ToPath(true);
@@ -363,15 +366,31 @@ public class BrushEngine : IDisposable
 
 
         if (paintable is { AnythingVisible: true })
         if (paintable is { AnythingVisible: true })
         {
         {
-            target.EnqueueDrawPath(path, paintable, vectorShape.StrokeWidth,
-                strokeCap, blendMode, strokeStyle, antiAliasing, null);
+            if (blender != null)
+            {
+                target.EnqueueDrawPath(path, paintable, vectorShape.StrokeWidth,
+                    strokeCap, blender, strokeStyle, antiAliasing, null);
+            }
+            else
+            {
+                target.EnqueueDrawPath(path, paintable, vectorShape.StrokeWidth,
+                    strokeCap, blendMode, strokeStyle, antiAliasing, null);
+            }
         }
         }
 
 
         if (fill is { AnythingVisible: true } && stroke is { AnythingVisible: true })
         if (fill is { AnythingVisible: true } && stroke is { AnythingVisible: true })
         {
         {
             strokeStyle = PaintStyle.Stroke;
             strokeStyle = PaintStyle.Stroke;
-            target.EnqueueDrawPath(path, stroke, vectorShape.StrokeWidth,
-                strokeCap, blendMode, strokeStyle, antiAliasing, null);
+            if (blender != null)
+            {
+                target.EnqueueDrawPath(path, stroke, vectorShape.StrokeWidth,
+                    strokeCap, blender, strokeStyle, antiAliasing, null);
+            }
+            else
+            {
+                target.EnqueueDrawPath(path, stroke, vectorShape.StrokeWidth,
+                    strokeCap, blendMode, strokeStyle, antiAliasing, null);
+            }
         }
         }
 
 
         if (content != null)
         if (content != null)
@@ -395,8 +414,16 @@ public class BrushEngine : IDisposable
                     brushPaintable = new TexturePaintable(new Texture(contentTexture), true);
                     brushPaintable = new TexturePaintable(new Texture(contentTexture), true);
                 }
                 }
 
 
-                target.EnqueueDrawPath(path, brushPaintable, vectorShape.StrokeWidth,
-                    StrokeCap.Butt, blendMode, PaintStyle.Fill, antiAliasing, null);
+                if (blender != null)
+                {
+                    target.EnqueueDrawPath(path, brushPaintable, vectorShape.StrokeWidth,
+                        StrokeCap.Butt, blender, PaintStyle.Fill, antiAliasing, null);
+                }
+                else
+                {
+                    target.EnqueueDrawPath(path, brushPaintable, vectorShape.StrokeWidth,
+                        StrokeCap.Butt, blendMode, PaintStyle.Fill, antiAliasing, null);
+                }
             }
             }
         }
         }
 
 

+ 44 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Brushes/BrushOutputNode.cs

@@ -3,6 +3,7 @@ using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -31,6 +32,21 @@ public class BrushOutputNode : Node
     public const int StrokePreviewSizeX = 200;
     public const int StrokePreviewSizeX = 200;
     public const int StrokePreviewSizeY = 50;
     public const int StrokePreviewSizeY = 50;
 
 
+    public const string DefaultBlenderCode = @"
+    vec4 main(vec4 src, vec4 dst) {
+    	return src + (1 - src.a) * dst;
+    }
+";
+
+    private string? lastStampBlenderCode = "";
+    private string? lastImageBlenderCode = "";
+
+    public Blender? LastStampBlender => cachedStampBlender;
+    public Blender? LastImageBlender => cachedImageBlender;
+
+    private Blender? cachedStampBlender = null;
+    private Blender? cachedImageBlender = null;
+
     public InputProperty<string> BrushName { get; }
     public InputProperty<string> BrushName { get; }
     public InputProperty<ShapeVectorData> VectorShape { get; }
     public InputProperty<ShapeVectorData> VectorShape { get; }
     public InputProperty<Paintable> Stroke { get; }
     public InputProperty<Paintable> Stroke { get; }
@@ -38,6 +54,8 @@ public class BrushOutputNode : Node
     public RenderInputProperty Content { get; }
     public RenderInputProperty Content { get; }
     public InputProperty<Drawie.Backend.Core.Surfaces.BlendMode> StampBlendMode { get; }
     public InputProperty<Drawie.Backend.Core.Surfaces.BlendMode> StampBlendMode { get; }
     public InputProperty<Drawie.Backend.Core.Surfaces.BlendMode> ImageBlendMode { get; }
     public InputProperty<Drawie.Backend.Core.Surfaces.BlendMode> ImageBlendMode { get; }
+    public InputProperty<bool> UseCustomStampBlender { get; }
+    public InputProperty<string> CustomStampBlenderCode { get; }
     public InputProperty<Matrix3X3> Transform { get; }
     public InputProperty<Matrix3X3> Transform { get; }
     public InputProperty<float> Pressure { get; }
     public InputProperty<float> Pressure { get; }
     public InputProperty<float> Spacing { get; }
     public InputProperty<float> Spacing { get; }
@@ -59,7 +77,7 @@ public class BrushOutputNode : Node
     private TextureCache cache = new();
     private TextureCache cache = new();
 
 
     private ChunkyImage? previewChunkyImage;
     private ChunkyImage? previewChunkyImage;
-    private BrushEngine previewEngine = new BrushEngine() { PressureSmoothingWindowSize = 0};
+    private BrushEngine previewEngine = new BrushEngine() { PressureSmoothingWindowSize = 0 };
 
 
     protected override bool ExecuteOnlyOnCacheChange => true;
     protected override bool ExecuteOnlyOnCacheChange => true;
     public Guid PersistentId { get; private set; } = Guid.NewGuid();
     public Guid PersistentId { get; private set; } = Guid.NewGuid();
@@ -83,6 +101,11 @@ public class BrushOutputNode : Node
             Drawie.Backend.Core.Surfaces.BlendMode.SrcOver);
             Drawie.Backend.Core.Surfaces.BlendMode.SrcOver);
         StampBlendMode = CreateInput<Drawie.Backend.Core.Surfaces.BlendMode>("StampBlendMode", "STAMP_BLEND_MODE",
         StampBlendMode = CreateInput<Drawie.Backend.Core.Surfaces.BlendMode>("StampBlendMode", "STAMP_BLEND_MODE",
             Drawie.Backend.Core.Surfaces.BlendMode.SrcOver);
             Drawie.Backend.Core.Surfaces.BlendMode.SrcOver);
+
+        UseCustomStampBlender = CreateInput<bool>("UseCustomStampBlender", "USE_CUSTOM_STAMP_BLENDER", false);
+
+        CustomStampBlenderCode =
+            CreateInput<string>("CustomStampBlenderCode", "CUSTOM_STAMP_BLENDER_CODE", DefaultBlenderCode);
         CanReuseStamps = CreateInput<bool>("CanReuseStamps", "CAN_REUSE_STAMPS", false);
         CanReuseStamps = CreateInput<bool>("CanReuseStamps", "CAN_REUSE_STAMPS", false);
 
 
         Pressure = CreateInput<float>("Pressure", "PRESSURE", 1f);
         Pressure = CreateInput<float>("Pressure", "PRESSURE", 1f);
@@ -110,6 +133,22 @@ public class BrushOutputNode : Node
             }
             }
         }
         }
 
 
+        if (UseCustomStampBlender.Value)
+        {
+            if (CustomStampBlenderCode.Value != lastStampBlenderCode || cachedStampBlender == null)
+            {
+                cachedStampBlender?.Dispose();
+                cachedStampBlender = Blender.CreateFromString(CustomStampBlenderCode.Value, out _);
+                lastStampBlenderCode = CustomStampBlenderCode.Value;
+            }
+        }
+        else
+        {
+            cachedStampBlender?.Dispose();
+            cachedStampBlender = null;
+            lastStampBlenderCode = "";
+        }
+
         RenderPreviews(context.GetPreviewTexturesForNode(Id), context);
         RenderPreviews(context.GetPreviewTexturesForNode(Id), context);
     }
     }
 
 
@@ -218,12 +257,13 @@ public class BrushOutputNode : Node
             var vec4D = previewVectorPath.GetPositionAndTangentAtDistance(offset, false);
             var vec4D = previewVectorPath.GetPositionAndTangentAtDistance(offset, false);
             pos = vec4D.XY;
             pos = vec4D.XY;
             pos = new VecD(pos.X, pos.Y + maxSize / 2f) + shift;
             pos = new VecD(pos.X, pos.Y + maxSize / 2f) + shift;
-            
+
             points.Add(new RecordedPoint((VecI)pos, new PointerInfo(pos, pressure, 0, VecD.Zero, vec4D.ZW),
             points.Add(new RecordedPoint((VecI)pos, new PointerInfo(pos, pressure, 0, VecD.Zero, vec4D.ZW),
                 new KeyboardInfo(), new EditorData(Colors.White, Colors.Black)));
                 new KeyboardInfo(), new EditorData(Colors.White, Colors.Black)));
 
 
             previewEngine.ExecuteBrush(target,
             previewEngine.ExecuteBrush(target,
-                new BrushData(context.Graph, Id) { StrokeWidth = maxSize, AntiAliasing = true }, points, context.FrameTime,
+                new BrushData(context.Graph, Id) { StrokeWidth = maxSize, AntiAliasing = true }, points,
+                context.FrameTime,
                 context.ProcessingColorSpace, context.DesiredSamplingOptions);
                 context.ProcessingColorSpace, context.DesiredSamplingOptions);
             offset += 1;
             offset += 1;
         }
         }
@@ -236,6 +276,7 @@ public class BrushOutputNode : Node
         {
         {
             previewVectorPath = VectorPath.FromSvgPath(PreviewSvg);
             previewVectorPath = VectorPath.FromSvgPath(PreviewSvg);
         }
         }
+
         List<RecordedPoint> points = new();
         List<RecordedPoint> points = new();
 
 
         float offset = 0;
         float offset = 0;

BIN
src/PixiEditor/Data/Brushes/BasicFlowOpacity.pixi


+ 3 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -1266,5 +1266,7 @@
   "TEXTURE": "Texture",
   "TEXTURE": "Texture",
   "PAINTER": "Painter",
   "PAINTER": "Painter",
   "VECTOR_PATH": "Vector Path",
   "VECTOR_PATH": "Vector Path",
-  "CAN_REUSE_STAMPS": "Can Reuse Stamps"
+  "CAN_REUSE_STAMPS": "Can Reuse Stamps",
+  "USE_CUSTOM_STAMP_BLENDER": "Use Custom Stamp Blender",
+  "CUSTOM_STAMP_BLENDER_CODE": "Stamp Blender"
 }
 }