Browse Source

Added custom blender

Krzysztof Krysiński 1 week ago
parent
commit
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.Paintables;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 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>
     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.Paintables;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Vector;
@@ -28,6 +29,15 @@ internal class PathOperation : IMirroredDrawOperation
         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,
         PaintStyle style, bool antiAliasing, RectI? customBounds = null)
     {
@@ -39,6 +49,16 @@ internal class PathOperation : IMirroredDrawOperation
         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)
     {
         paint.IsAntiAliased = antiAliasing || targetChunk.Resolution != ChunkResolution.Full;
@@ -68,8 +88,20 @@ internal class PathOperation : IMirroredDrawOperation
             newBounds = (RectI)newBounds.ReflectY((double)horAxisY).Round();
         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);

+ 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.Paintables;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -320,9 +321,11 @@ public class BrushEngine : IDisposable
         var stroke = brushNode.Stroke.Value;
         bool snapToPixels = brushNode.SnapToPixels.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,
-                brushNode.StampBlendMode.Value, antiAliasing, fill, stroke, snapToPixels, canReuseStamps))
+                stampBlender, brushNode.StampBlendMode.Value, antiAliasing, fill, stroke, snapToPixels, canReuseStamps))
         {
             lastPos = point;
         }
@@ -330,7 +333,7 @@ public class BrushEngine : IDisposable
 
     public bool PaintBrush(ChunkyImage target, bool autoPosition, ShapeVectorData vectorShape,
         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)
     {
         var path = vectorShape.ToPath(true);
@@ -363,15 +366,31 @@ public class BrushEngine : IDisposable
 
         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 })
         {
             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)
@@ -395,8 +414,16 @@ public class BrushEngine : IDisposable
                     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.Paintables;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Shaders;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -31,6 +32,21 @@ public class BrushOutputNode : Node
     public const int StrokePreviewSizeX = 200;
     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<ShapeVectorData> VectorShape { get; }
     public InputProperty<Paintable> Stroke { get; }
@@ -38,6 +54,8 @@ public class BrushOutputNode : Node
     public RenderInputProperty Content { get; }
     public InputProperty<Drawie.Backend.Core.Surfaces.BlendMode> StampBlendMode { 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<float> Pressure { get; }
     public InputProperty<float> Spacing { get; }
@@ -59,7 +77,7 @@ public class BrushOutputNode : Node
     private TextureCache cache = new();
 
     private ChunkyImage? previewChunkyImage;
-    private BrushEngine previewEngine = new BrushEngine() { PressureSmoothingWindowSize = 0};
+    private BrushEngine previewEngine = new BrushEngine() { PressureSmoothingWindowSize = 0 };
 
     protected override bool ExecuteOnlyOnCacheChange => true;
     public Guid PersistentId { get; private set; } = Guid.NewGuid();
@@ -83,6 +101,11 @@ public class BrushOutputNode : Node
             Drawie.Backend.Core.Surfaces.BlendMode.SrcOver);
         StampBlendMode = CreateInput<Drawie.Backend.Core.Surfaces.BlendMode>("StampBlendMode", "STAMP_BLEND_MODE",
             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);
 
         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);
     }
 
@@ -218,12 +257,13 @@ public class BrushOutputNode : Node
             var vec4D = previewVectorPath.GetPositionAndTangentAtDistance(offset, false);
             pos = vec4D.XY;
             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),
                 new KeyboardInfo(), new EditorData(Colors.White, Colors.Black)));
 
             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);
             offset += 1;
         }
@@ -236,6 +276,7 @@ public class BrushOutputNode : Node
         {
             previewVectorPath = VectorPath.FromSvgPath(PreviewSvg);
         }
+
         List<RecordedPoint> points = new();
 
         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",
   "PAINTER": "Painter",
   "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"
 }