Browse Source

Paintables wip

Krzysztof Krysiński 5 months ago
parent
commit
b6d543ff87
68 changed files with 578 additions and 550 deletions
  1. 4 3
      src/ChunkyImageLib/ChunkyImage.cs
  2. 0 295
      src/ChunkyImageLib/DataHolders/ShapeCorners.cs
  3. 8 7
      src/ChunkyImageLib/DataHolders/ShapeData.cs
  4. 14 5
      src/ChunkyImageLib/Operations/BresenhamLineOperation.cs
  5. 20 19
      src/ChunkyImageLib/Operations/EllipseOperation.cs
  6. 8 8
      src/ChunkyImageLib/Operations/RectangleOperation.cs
  7. 1 1
      src/Drawie
  8. 2 0
      src/PixiEditor.ChangeableDocument.Gen/Helpers.cs
  9. 3 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/TransformReferenceLayer_ChangeInfo.cs
  10. 3 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyShapeVectorData.cs
  11. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyStructureNode.cs
  12. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs
  13. 3 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/LineVectorData.cs
  14. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs
  15. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PointsVectorData.cs
  16. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs
  17. 5 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/ShapeVectorData.cs
  18. 4 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs
  19. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/EllipseNode.cs
  20. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  21. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs
  22. 5 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRasterEllipse_UpdateableChange.cs
  23. 10 8
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRasterLine_UpdateableChange.cs
  24. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/PreviewShiftLayers_UpdateableChange.cs
  25. 2 1
      src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/TransformReferenceLayer_UpdateableChange.cs
  26. 3 2
      src/PixiEditor.ChangeableDocument/Changes/Vectors/ConvertToCurve_Change.cs
  27. 1 0
      src/PixiEditor/Helpers/DocumentViewModelBuilder.cs
  28. 60 5
      src/PixiEditor/Helpers/Extensions/ColorHelpers.cs
  29. 6 5
      src/PixiEditor/Helpers/Resources/VectorPathResource.cs
  30. 1 0
      src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs
  31. 41 25
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs
  32. 1 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/Features/ITransformableExecutor.cs
  33. 13 8
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs
  34. 4 4
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterEllipseToolExecutor.cs
  35. 3 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterLineToolExecutor.cs
  36. 2 2
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterRectangleToolExecutor.cs
  37. 1 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs
  38. 1 6
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs
  39. 5 5
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorEllipseToolExecutor.cs
  40. 10 10
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs
  41. 5 5
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorRectangleToolExecutor.cs
  42. 8 7
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs
  43. 1 0
      src/PixiEditor/Models/Handlers/IColorsHandler.cs
  44. 1 1
      src/PixiEditor/Models/Handlers/Toolbars/IFillableShapeToolbar.cs
  45. 1 1
      src/PixiEditor/Models/Handlers/Toolbars/IShapeToolbar.cs
  46. 2 2
      src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs
  47. 5 5
      src/PixiEditor/Models/Serialization/Factories/EllipseSerializationFactory.cs
  48. 5 5
      src/PixiEditor/Models/Serialization/Factories/LineSerializationFactory.cs
  49. 44 0
      src/PixiEditor/Models/Serialization/Factories/Paintables/ColorPaintableSerializationFactory.cs
  50. 9 0
      src/PixiEditor/Models/Serialization/Factories/Paintables/IPaintableSerializationFactory.cs
  51. 6 5
      src/PixiEditor/Models/Serialization/Factories/PointsDataSerializationFactory.cs
  52. 5 5
      src/PixiEditor/Models/Serialization/Factories/RectangleSerializationFactory.cs
  53. 6 4
      src/PixiEditor/Models/Serialization/Factories/TextSerializationFactory.cs
  54. 6 6
      src/PixiEditor/Models/Serialization/Factories/VectorPathSerializationFactory.cs
  55. 82 17
      src/PixiEditor/Models/Serialization/Factories/VectorShapeSerializationFactory.cs
  56. 18 6
      src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs
  57. 1 0
      src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs
  58. 34 3
      src/PixiEditor/ViewModels/Tools/ToolSettings/Settings/ColorSettingViewModel.cs
  59. 5 0
      src/PixiEditor/ViewModels/Tools/ToolSettings/Settings/Setting.cs
  60. 4 4
      src/PixiEditor/ViewModels/Tools/ToolSettings/Toolbars/FillableShapeToolbar.cs
  61. 4 4
      src/PixiEditor/ViewModels/Tools/ToolSettings/Toolbars/ShapeToolbar.cs
  62. 3 2
      src/PixiEditor/ViewModels/Tools/ToolSettings/Toolbars/ToolbarFactory.cs
  63. 1 1
      src/PixiEditor/Views/Input/ToolSettingColorPicker.axaml
  64. 6 6
      src/PixiEditor/Views/Input/ToolSettingColorPicker.axaml.cs
  65. 1 1
      src/PixiEditor/Views/Tools/ToolSettings/Settings/ColorSettingView.axaml
  66. 1 1
      src/colorpicker
  67. 5 4
      tests/PixiEditor.Backend.Tests/NodeSystemTests.cs
  68. 47 0
      tests/PixiEditor.Tests/SerializationTests.cs

+ 4 - 3
src/ChunkyImageLib/ChunkyImage.cs

@@ -7,6 +7,7 @@ using OneOf.Types;
 using PixiEditor.Common;
 using PixiEditor.Common;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
@@ -610,7 +611,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     }
     }
 
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawEllipse(RectD location, Color strokeColor, Color fillColor, float strokeWidth,
+    public void EnqueueDrawEllipse(RectD location, Paintable strokeColor, Paintable fillColor, float strokeWidth,
         double rotationRad = 0, bool antiAliased = false,
         double rotationRad = 0, bool antiAliased = false,
         Paint? paint = null)
         Paint? paint = null)
     {
     {
@@ -737,12 +738,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     }
     }
 
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawBresenhamLine(VecI from, VecI to, Color color, BlendMode blendMode)
+    public void EnqueueDrawBresenhamLine(VecI from, VecI to, Paintable paintable, BlendMode blendMode)
     {
     {
         lock (lockObject)
         lock (lockObject)
         {
         {
             ThrowIfDisposed();
             ThrowIfDisposed();
-            BresenhamLineOperation operation = new(from, to, color, blendMode);
+            BresenhamLineOperation operation = new(from, to, paintable, blendMode);
             EnqueueOperation(operation);
             EnqueueOperation(operation);
         }
         }
     }
     }

+ 0 - 295
src/ChunkyImageLib/DataHolders/ShapeCorners.cs

@@ -1,295 +0,0 @@
-using System.Diagnostics;
-using Drawie.Backend.Core.Numerics;
-using Drawie.Numerics;
-
-namespace ChunkyImageLib.DataHolders;
-
-[DebuggerDisplay("TL: {TopLeft}, TR: {TopRight}, BL: {BottomLeft}, BR: {BottomRight}")]
-public struct ShapeCorners
-{
-    private const double epsilon = 0.001;
-
-    public ShapeCorners(VecD center, VecD size)
-    {
-        TopLeft = center - size / 2;
-        TopRight = center + new VecD(size.X / 2, -size.Y / 2);
-        BottomRight = center + size / 2;
-        BottomLeft = center + new VecD(-size.X / 2, size.Y / 2);
-    }
-
-    public ShapeCorners(RectD rect)
-    {
-        TopLeft = rect.TopLeft;
-        TopRight = rect.TopRight;
-        BottomRight = rect.BottomRight;
-        BottomLeft = rect.BottomLeft;
-    }
-
-    public VecD TopLeft { get; set; }
-    public VecD TopRight { get; set; }
-    public VecD BottomLeft { get; set; }
-    public VecD BottomRight { get; set; }
-
-    public bool IsInverted
-    {
-        get
-        {
-            var top = TopLeft - TopRight;
-            var right = TopRight - BottomRight;
-            var bottom = BottomRight - BottomLeft;
-            var left = BottomLeft - TopLeft;
-            return Math.Sign(top.Cross(right)) + Math.Sign(right.Cross(bottom)) + Math.Sign(bottom.Cross(left)) +
-                Math.Sign(left.Cross(top)) < 0;
-        }
-    }
-
-    public bool IsLegal
-    {
-        get
-        {
-            if (HasNaNOrInfinity)
-                return false;
-            var top = TopLeft - TopRight;
-            var right = TopRight - BottomRight;
-            var bottom = BottomRight - BottomLeft;
-            var left = BottomLeft - TopLeft;
-            var topRight = Math.Sign(top.Cross(right));
-            return topRight == Math.Sign(right.Cross(bottom)) && topRight == Math.Sign(bottom.Cross(left)) &&
-                   topRight == Math.Sign(left.Cross(top));
-        }
-    }
-
-    /// <summary>
-    /// Checks if two or more corners are in the same position
-    /// </summary>
-    public bool IsPartiallyDegenerate
-    {
-        get
-        {
-            Span<VecD> lengths = stackalloc[]
-            {
-                TopLeft - TopRight, TopRight - BottomRight, BottomRight - BottomLeft, BottomLeft - TopLeft,
-                TopLeft - BottomRight, TopRight - BottomLeft
-            };
-            foreach (VecD vec in lengths)
-            {
-                if (vec.LengthSquared < epsilon * epsilon)
-                    return true;
-            }
-
-            return false;
-        }
-    }
-
-    public bool HasNaNOrInfinity => TopLeft.IsNaNOrInfinity() || TopRight.IsNaNOrInfinity() ||
-                                    BottomLeft.IsNaNOrInfinity() || BottomRight.IsNaNOrInfinity();
-
-    public bool IsRect => Math.Abs((TopLeft - BottomRight).Length - (TopRight - BottomLeft).Length) < epsilon;
-    public VecD RectSize => new((TopLeft - TopRight).Length, (TopLeft - BottomLeft).Length);
-    public VecD RectCenter => (TopLeft - BottomRight) / 2 + BottomRight;
-
-    public double RectRotation =>
-        (TopLeft - TopRight).Cross(TopLeft - BottomLeft) > 0
-            ? RectSize.CCWAngleTo(BottomRight - TopLeft)
-            : RectSize.CCWAngleTo(BottomLeft - TopRight);
-
-    public bool IsAlignedToPixels
-    {
-        get
-        {
-            double epsilon = 0.01;
-            return
-                (TopLeft - TopLeft.Round()).TaxicabLength < epsilon &&
-                (TopRight - TopRight.Round()).TaxicabLength < epsilon &&
-                (BottomLeft - BottomLeft.Round()).TaxicabLength < epsilon &&
-                (BottomRight - BottomRight.Round()).TaxicabLength < epsilon;
-        }
-    }
-
-    public RectD AABBBounds
-    {
-        get
-        {
-            double minX = Math.Min(Math.Min(TopLeft.X, TopRight.X), Math.Min(BottomLeft.X, BottomRight.X));
-            double minY = Math.Min(Math.Min(TopLeft.Y, TopRight.Y), Math.Min(BottomLeft.Y, BottomRight.Y));
-            double maxX = Math.Max(Math.Max(TopLeft.X, TopRight.X), Math.Max(BottomLeft.X, BottomRight.X));
-            double maxY = Math.Max(Math.Max(TopLeft.Y, TopRight.Y), Math.Max(BottomLeft.Y, BottomRight.Y));
-            return RectD.FromTwoPoints(new VecD(minX, minY), new VecD(maxX, maxY));
-        }
-    }
-
-    public bool IsPointInside(VecD point)
-    {
-        var top = TopLeft - TopRight;
-        var right = TopRight - BottomRight;
-        var bottom = BottomRight - BottomLeft;
-        var left = BottomLeft - TopLeft;
-
-        var deltaTopLeft = point - TopLeft;
-        var deltaTopRight = point - TopRight;
-        var deltaBottomRight = point - BottomRight;
-        var deltaBottomLeft = point - BottomLeft;
-
-        if (deltaTopRight.IsNaNOrInfinity() || deltaTopLeft.IsNaNOrInfinity() || deltaBottomRight.IsNaNOrInfinity() ||
-            deltaBottomRight.IsNaNOrInfinity())
-            return false;
-
-        var crossTop = Math.Sign(top.Cross(deltaTopLeft));
-        var crossRight = Math.Sign(right.Cross(deltaTopRight));
-        var crossBottom = Math.Sign(bottom.Cross(deltaBottomRight));
-        var crossLeft = Math.Sign(left.Cross(deltaBottomLeft));
-
-        return crossTop == crossRight && crossTop == crossLeft && crossTop == crossBottom;
-    }
-
-    public ShapeCorners AsMirroredAcrossHorAxis(double horAxisY) => new ShapeCorners
-    {
-        BottomLeft = BottomLeft.ReflectY(horAxisY),
-        BottomRight = BottomRight.ReflectY(horAxisY),
-        TopLeft = TopLeft.ReflectY(horAxisY),
-        TopRight = TopRight.ReflectY(horAxisY)
-    };
-
-    public ShapeCorners AsMirroredAcrossVerAxis(double verAxisX) => new ShapeCorners
-    {
-        BottomLeft = BottomLeft.ReflectX(verAxisX),
-        BottomRight = BottomRight.ReflectX(verAxisX),
-        TopLeft = TopLeft.ReflectX(verAxisX),
-        TopRight = TopRight.ReflectX(verAxisX)
-    };
-
-    public ShapeCorners AsRotated(double angleRad, VecD around) => new ShapeCorners
-    {
-        BottomLeft = BottomLeft.Rotate(angleRad, around),
-        BottomRight = BottomRight.Rotate(angleRad, around),
-        TopLeft = TopLeft.Rotate(angleRad, around),
-        TopRight = TopRight.Rotate(angleRad, around)
-    };
-
-    public ShapeCorners AsTranslated(VecD delta) => new ShapeCorners
-    {
-        BottomLeft = BottomLeft + delta,
-        BottomRight = BottomRight + delta,
-        TopLeft = TopLeft + delta,
-        TopRight = TopRight + delta
-    };
-
-    public ShapeCorners AsScaled(float uniformScale)
-    {
-        VecD center = RectCenter;
-        VecD topLeftDelta = TopLeft - center;
-        VecD topRightDelta = TopRight - center;
-        VecD bottomLeftDelta = BottomLeft - center;
-        VecD bottomRightDelta = BottomRight - center;
-
-        topLeftDelta *= uniformScale;
-        topRightDelta *= uniformScale;
-        bottomLeftDelta *= uniformScale;
-        bottomRightDelta *= uniformScale;
-
-        return new ShapeCorners()
-        {
-            TopLeft = center + topLeftDelta,
-            TopRight = center + topRightDelta,
-            BottomLeft = center + bottomLeftDelta,
-            BottomRight = center + bottomRightDelta
-        };
-    }
-
-    public ShapeCorners AsScaled(float scaleX, float scaleY)
-    {
-        VecD center = RectCenter;
-        VecD topLeftDelta = TopLeft - center;
-        VecD topRightDelta = TopRight - center;
-        VecD bottomLeftDelta = BottomLeft - center;
-        VecD bottomRightDelta = BottomRight - center;
-
-        topLeftDelta = new VecD(topLeftDelta.X * scaleX, topLeftDelta.Y * scaleY);
-        topRightDelta = new VecD(topRightDelta.X * scaleX, topRightDelta.Y * scaleY);
-        bottomLeftDelta = new VecD(bottomLeftDelta.X * scaleX, bottomLeftDelta.Y * scaleY);
-        bottomRightDelta = new VecD(bottomRightDelta.X * scaleX, bottomRightDelta.Y * scaleY);
-
-        return new ShapeCorners()
-        {
-            TopLeft = center + topLeftDelta,
-            TopRight = center + topRightDelta,
-            BottomLeft = center + bottomLeftDelta,
-            BottomRight = center + bottomRightDelta
-        };
-    }
-
-    public static bool operator !=(ShapeCorners left, ShapeCorners right) => !(left == right);
-
-    public static bool operator ==(ShapeCorners left, ShapeCorners right)
-    {
-        return
-            left.TopLeft == right.TopLeft &&
-            left.TopRight == right.TopRight &&
-            left.BottomLeft == right.BottomLeft &&
-            left.BottomRight == right.BottomRight;
-    }
-
-    public bool AlmostEquals(ShapeCorners other, double epsilon = 0.001)
-    {
-        return
-            TopLeft.AlmostEquals(other.TopLeft, epsilon) &&
-            TopRight.AlmostEquals(other.TopRight, epsilon) &&
-            BottomLeft.AlmostEquals(other.BottomLeft, epsilon) &&
-            BottomRight.AlmostEquals(other.BottomRight, epsilon);
-    }
-
-    public bool Intersects(RectD rect)
-    {
-        // Get all corners
-        VecD[] corners1 = { TopLeft, TopRight, BottomRight, BottomLeft };
-        VecD[] corners2 = { rect.TopLeft, rect.TopRight, rect.BottomRight, rect.BottomLeft };
-
-        // For each pair of corners
-        for (int i = 0; i < 4; i++)
-        {
-            VecD axis = corners1[i] - corners1[(i + 1) % 4]; // Create an edge
-            axis = new VecD(-axis.Y, axis.X); // Get perpendicular axis
-
-            // Project corners of first shape onto axis
-            double min1 = double.MaxValue, max1 = double.MinValue;
-            foreach (VecD corner in corners1)
-            {
-                double projection = corner.Dot(axis);
-                min1 = Math.Min(min1, projection);
-                max1 = Math.Max(max1, projection);
-            }
-
-            // Project corners of second shape onto axis
-            double min2 = double.MaxValue, max2 = double.MinValue;
-            foreach (VecD corner in corners2)
-            {
-                double projection = corner.Dot(axis);
-                min2 = Math.Min(min2, projection);
-                max2 = Math.Max(max2, projection);
-            }
-
-            // Check for overlap
-            if (max1 < min2 || max2 < min1)
-            {
-                // The projections do not overlap, so the shapes do not intersect
-                return false;
-            }
-        }
-
-        // All projections overlap, so the shapes intersect
-        return true;
-    }
-
-    public ShapeCorners WithMatrix(Matrix3X3 transformationMatrix)
-    {
-        ShapeCorners corners = new ShapeCorners
-        {
-            TopLeft = transformationMatrix.MapPoint(TopLeft),
-            TopRight = transformationMatrix.MapPoint(TopRight),
-            BottomLeft = transformationMatrix.MapPoint(BottomLeft),
-            BottomRight = transformationMatrix.MapPoint(BottomRight)
-        };
-
-        return corners;
-    }
-}

+ 8 - 7
src/ChunkyImageLib/DataHolders/ShapeData.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Numerics;
 using Drawie.Numerics;
@@ -7,18 +8,18 @@ namespace ChunkyImageLib.DataHolders;
 
 
 public record struct ShapeData
 public record struct ShapeData
 {
 {
-    public ShapeData(VecD center, VecD size, double rotation, float strokeWidth, Color strokeColor, Color fillColor, BlendMode blendMode = BlendMode.SrcOver)
+    public ShapeData(VecD center, VecD size, double rotation, float strokeWidth, Paintable stroke, Paintable fillPaintable, BlendMode blendMode = BlendMode.SrcOver)
     {
     {
-        StrokeColor = strokeColor;
-        FillColor = fillColor;
+        Stroke = stroke;
+        FillPaintable = fillPaintable;
         Center = center;
         Center = center;
         Size = size;
         Size = size;
         Angle = rotation;
         Angle = rotation;
         StrokeWidth = strokeWidth;
         StrokeWidth = strokeWidth;
         BlendMode = blendMode;
         BlendMode = blendMode;
     }
     }
-    public Color StrokeColor { get; }
-    public Color FillColor { get; }
+    public Paintable Stroke { get; }
+    public Paintable FillPaintable { get; }
     public BlendMode BlendMode { get; }
     public BlendMode BlendMode { get; }
     public VecD Center { get; }
     public VecD Center { get; }
 
 
@@ -31,8 +32,8 @@ public record struct ShapeData
     
     
 
 
     public ShapeData AsMirroredAcrossHorAxis(double horAxisY)
     public ShapeData AsMirroredAcrossHorAxis(double horAxisY)
-        => new ShapeData(Center.ReflectY(horAxisY), new(Size.X, -Size.Y), -Angle, StrokeWidth, StrokeColor, FillColor, BlendMode);
+        => new ShapeData(Center.ReflectY(horAxisY), new(Size.X, -Size.Y), -Angle, StrokeWidth, Stroke, FillPaintable, BlendMode);
     public ShapeData AsMirroredAcrossVerAxis(double verAxisX)
     public ShapeData AsMirroredAcrossVerAxis(double verAxisX)
-        => new ShapeData(Center.ReflectX(verAxisX), new(-Size.X, Size.Y), -Angle, StrokeWidth, StrokeColor, FillColor, BlendMode);
+        => new ShapeData(Center.ReflectX(verAxisX), new(-Size.X, Size.Y), -Angle, StrokeWidth, Stroke, FillPaintable, BlendMode);
 
 
 }
 }

+ 14 - 5
src/ChunkyImageLib/Operations/BresenhamLineOperation.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -11,16 +12,16 @@ internal class BresenhamLineOperation : IMirroredDrawOperation
     public bool IgnoreEmptyChunks => false;
     public bool IgnoreEmptyChunks => false;
     private readonly VecI from;
     private readonly VecI from;
     private readonly VecI to;
     private readonly VecI to;
-    private readonly Color color;
+    private readonly Paintable paintable;
     private readonly BlendMode blendMode;
     private readonly BlendMode blendMode;
     private readonly VecF[] points;
     private readonly VecF[] points;
     private Paint paint;
     private Paint paint;
 
 
-    public BresenhamLineOperation(VecI from, VecI to, Color color, BlendMode blendMode)
+    public BresenhamLineOperation(VecI from, VecI to, Paintable paintable, BlendMode blendMode)
     {
     {
         this.from = from;
         this.from = from;
         this.to = to;
         this.to = to;
-        this.color = color;
+        this.paintable = paintable;
         this.blendMode = blendMode;
         this.blendMode = blendMode;
         paint = new Paint() { BlendMode = blendMode };
         paint = new Paint() { BlendMode = blendMode };
         points = BresenhamLineHelper.GetBresenhamLine(from, to).Select(v => new VecF(v)).ToArray();
         points = BresenhamLineHelper.GetBresenhamLine(from, to).Select(v => new VecF(v)).ToArray();
@@ -29,7 +30,15 @@ internal class BresenhamLineOperation : IMirroredDrawOperation
     public void DrawOnChunk(Chunk targetChunk, VecI chunkPos)
     public void DrawOnChunk(Chunk targetChunk, VecI chunkPos)
     {
     {
         // a hacky way to make the lines look slightly better on non full res chunks
         // a hacky way to make the lines look slightly better on non full res chunks
-        paint.Color = new Color(color.R, color.G, color.B, (byte)(color.A * targetChunk.Resolution.Multiplier()));
+        if (paintable is ColorPaintable colorPaintable)
+        {
+            paint.Color = new Color(colorPaintable.Color.R, colorPaintable.Color.G, colorPaintable.Color.B,
+                (byte)(colorPaintable.Color.A * targetChunk.Resolution.Multiplier()));
+        }
+        else
+        {
+            paint.SetPaintable(paintable);
+        }
 
 
         var surf = targetChunk.Surface.DrawingSurface;
         var surf = targetChunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Save();
@@ -59,7 +68,7 @@ internal class BresenhamLineOperation : IMirroredDrawOperation
             newFrom = (RectI)newFrom.ReflectY((double)horAxisY).Round();
             newFrom = (RectI)newFrom.ReflectY((double)horAxisY).Round();
             newTo = (RectI)newTo.ReflectY((double)horAxisY).Round();
             newTo = (RectI)newTo.ReflectY((double)horAxisY).Round();
         }
         }
-        return new BresenhamLineOperation(newFrom.Pos, newTo.Pos, color, blendMode);
+        return new BresenhamLineOperation(newFrom.Pos, newTo.Pos, paintable, blendMode);
     }
     }
 
 
     public void Dispose()
     public void Dispose()

+ 20 - 19
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -12,8 +13,8 @@ internal class EllipseOperation : IMirroredDrawOperation
     public bool IgnoreEmptyChunks => false;
     public bool IgnoreEmptyChunks => false;
 
 
     private readonly RectD location;
     private readonly RectD location;
-    private readonly Color strokeColor;
-    private readonly Color fillColor;
+    private readonly Paintable strokePaintable;
+    private readonly Paintable fillPaintable;
     private readonly float strokeWidth;
     private readonly float strokeWidth;
     private readonly double rotation;
     private readonly double rotation;
     private readonly Paint paint;
     private readonly Paint paint;
@@ -27,12 +28,12 @@ internal class EllipseOperation : IMirroredDrawOperation
     private RectI? ellipseFillRect;
     private RectI? ellipseFillRect;
     private bool antialiased;
     private bool antialiased;
 
 
-    public EllipseOperation(RectD location, Color strokeColor, Color fillColor, float strokeWidth, double rotationRad,
+    public EllipseOperation(RectD location, Paintable strokePaintable, Paintable fillPaintable, float strokeWidth, double rotationRad,
         bool antiAliased, Paint? paint = null)
         bool antiAliased, Paint? paint = null)
     {
     {
         this.location = location;
         this.location = location;
-        this.strokeColor = strokeColor;
-        this.fillColor = fillColor;
+        this.strokePaintable = strokePaintable;
+        this.fillPaintable = fillPaintable;
         this.strokeWidth = strokeWidth;
         this.strokeWidth = strokeWidth;
         this.rotation = rotationRad;
         this.rotation = rotationRad;
         this.paint = paint?.Clone() ?? new Paint();
         this.paint = paint?.Clone() ?? new Paint();
@@ -56,7 +57,7 @@ internal class EllipseOperation : IMirroredDrawOperation
 
 
                     ellipse = ellipseList.Select(a => new VecF(a)).ToArray();
                     ellipse = ellipseList.Select(a => new VecF(a)).ToArray();
 
 
-                    if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
+                    if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                     {
                     {
                         (var fill, ellipseFillRect) =
                         (var fill, ellipseFillRect) =
                             EllipseHelper.SplitEllipseFillIntoRegions(ellipseList.ToList(), (RectI)location);
                             EllipseHelper.SplitEllipseFillIntoRegions(ellipseList.ToList(), (RectI)location);
@@ -108,14 +109,14 @@ internal class EllipseOperation : IMirroredDrawOperation
         {
         {
             if (Math.Abs(rotation) < 0.001 && strokeWidth > 0)
             if (Math.Abs(rotation) < 0.001 && strokeWidth > 0)
             {
             {
-                if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
+                if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                 {
                 {
-                    paint.Color = fillColor;
+                    paint.SetPaintable(fillPaintable);
                     surf.Canvas.DrawPoints(PointMode.Lines, ellipseFill!, paint);
                     surf.Canvas.DrawPoints(PointMode.Lines, ellipseFill!, paint);
                     surf.Canvas.DrawRect((RectD)ellipseFillRect!.Value, paint);
                     surf.Canvas.DrawRect((RectD)ellipseFillRect!.Value, paint);
                 }
                 }
                 
                 
-                paint.Color = strokeWidth <= 0 ? fillColor : strokeColor;
+                paint.SetPaintable(strokeWidth <= 0 ? fillPaintable : strokePaintable);
                 paint.StrokeWidth = 1f;
                 paint.StrokeWidth = 1f;
                 surf.Canvas.DrawPoints(PointMode.Points, ellipse!, paint);
                 surf.Canvas.DrawPoints(PointMode.Points, ellipse!, paint);
             }
             }
@@ -124,16 +125,16 @@ internal class EllipseOperation : IMirroredDrawOperation
                 surf.Canvas.Save();
                 surf.Canvas.Save();
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
                 
                 
-                if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
+                if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                 {
                 {
-                    paint.Color = fillColor;
+                    paint.SetPaintable(fillPaintable);
                     paint.Style = PaintStyle.Fill;
                     paint.Style = PaintStyle.Fill;
                     surf.Canvas.DrawPath(ellipseOutline!, paint);
                     surf.Canvas.DrawPath(ellipseOutline!, paint);
                 }
                 }
 
 
                 if (strokeWidth > 0)
                 if (strokeWidth > 0)
                 {
                 {
-                    paint.Color = strokeColor;
+                    paint.SetPaintable(strokePaintable);
                     paint.Style = PaintStyle.Stroke;
                     paint.Style = PaintStyle.Stroke;
                     paint.StrokeWidth = 1;
                     paint.StrokeWidth = 1;
 
 
@@ -145,19 +146,19 @@ internal class EllipseOperation : IMirroredDrawOperation
         }
         }
         else
         else
         {
         {
-            if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
+            if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
             {
             {
                 surf.Canvas.Save();
                 surf.Canvas.Save();
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
                 surf.Canvas.ClipPath(innerPath!);
                 surf.Canvas.ClipPath(innerPath!);
-                surf.Canvas.DrawColor(fillColor, paint.BlendMode);
+                surf.Canvas.DrawPaintable(fillPaintable, paint.BlendMode);
                 surf.Canvas.Restore();
                 surf.Canvas.Restore();
             }
             }
             surf.Canvas.Save();
             surf.Canvas.Save();
             surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
             surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
             surf.Canvas.ClipPath(outerPath!);
             surf.Canvas.ClipPath(outerPath!);
             surf.Canvas.ClipPath(innerPath!, ClipOperation.Difference);
             surf.Canvas.ClipPath(innerPath!, ClipOperation.Difference);
-            surf.Canvas.DrawColor(strokeColor, paint.BlendMode);
+            surf.Canvas.DrawPaintable(strokePaintable, paint.BlendMode);
             surf.Canvas.Restore();
             surf.Canvas.Restore();
         }
         }
     }
     }
@@ -168,7 +169,7 @@ internal class EllipseOperation : IMirroredDrawOperation
         surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
         surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
         
         
         paint.IsAntiAliased = false;
         paint.IsAntiAliased = false;
-        paint.Color = fillColor;
+        paint.SetPaintable(fillPaintable);
         paint.Style = PaintStyle.Fill;
         paint.Style = PaintStyle.Fill;
         
         
         RectD fillRect = ((RectD)location).Inflate(-strokeWidth / 2f);
         RectD fillRect = ((RectD)location).Inflate(-strokeWidth / 2f);
@@ -176,7 +177,7 @@ internal class EllipseOperation : IMirroredDrawOperation
         surf.Canvas.DrawOval(fillRect.Center, fillRect.Size / 2f, paint);
         surf.Canvas.DrawOval(fillRect.Center, fillRect.Size / 2f, paint);
         
         
         paint.IsAntiAliased = true;
         paint.IsAntiAliased = true;
-        paint.Color = strokeWidth <= 0 ? fillColor : strokeColor;
+        paint.SetPaintable(strokeWidth <= 0 ? fillPaintable : strokePaintable);
         paint.Style = PaintStyle.Stroke;
         paint.Style = PaintStyle.Stroke;
         paint.StrokeWidth = strokeWidth <= 0 ? 1f : strokeWidth;
         paint.StrokeWidth = strokeWidth <= 0 ? 1f : strokeWidth;
         
         
@@ -194,7 +195,7 @@ internal class EllipseOperation : IMirroredDrawOperation
         RectI bounds = (RectI)corners.AABBBounds.RoundOutwards();
         RectI bounds = (RectI)corners.AABBBounds.RoundOutwards();
         
         
         var chunks = OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize);
         var chunks = OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize);
-        if (fillColor.A == 0)
+        if (!fillPaintable.AnythingVisible)
         {
         {
              chunks.ExceptWith(OperationHelper.FindChunksFullyInsideEllipse
              chunks.ExceptWith(OperationHelper.FindChunksFullyInsideEllipse
                 (location.Center, location.Width / 2.0 - strokeWidth * 2, location.Height / 2.0 - strokeWidth * 2, ChunkyImage.FullChunkSize, rotation));
                 (location.Center, location.Width / 2.0 - strokeWidth * 2, location.Height / 2.0 - strokeWidth * 2, ChunkyImage.FullChunkSize, rotation));
@@ -210,7 +211,7 @@ internal class EllipseOperation : IMirroredDrawOperation
             newLocation = newLocation.ReflectX((double)verAxisX).Round();
             newLocation = newLocation.ReflectX((double)verAxisX).Round();
         if (horAxisY is not null)
         if (horAxisY is not null)
             newLocation = newLocation.ReflectY((double)horAxisY).Round();
             newLocation = newLocation.ReflectY((double)horAxisY).Round();
-        return new EllipseOperation(newLocation, strokeColor, fillColor, strokeWidth, rotation, antialiased, paint);
+        return new EllipseOperation(newLocation, strokePaintable, fillPaintable, strokeWidth, rotation, antialiased, paint);
     }
     }
 
 
     public void Dispose()
     public void Dispose()

+ 8 - 8
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -54,11 +54,11 @@ internal class RectangleOperation : IMirroredDrawOperation
     private void DrawPixelPerfect(DrawingSurface surf, RectD rect, RectD innerRect)
     private void DrawPixelPerfect(DrawingSurface surf, RectD rect, RectD innerRect)
     {
     {
         // draw fill
         // draw fill
-        if (Data.FillColor.A > 0)
+        if (Data.FillPaintable.AnythingVisible)
         {
         {
             int saved = surf.Canvas.Save();
             int saved = surf.Canvas.Save();
             surf.Canvas.ClipRect(innerRect);
             surf.Canvas.ClipRect(innerRect);
-            surf.Canvas.DrawColor(Data.FillColor, Data.BlendMode);
+            surf.Canvas.DrawPaintable(Data.FillPaintable, Data.BlendMode);
             surf.Canvas.RestoreToCount(saved);
             surf.Canvas.RestoreToCount(saved);
         }
         }
 
 
@@ -66,18 +66,18 @@ internal class RectangleOperation : IMirroredDrawOperation
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.ClipRect(rect);
         surf.Canvas.ClipRect(rect);
         surf.Canvas.ClipRect(innerRect, ClipOperation.Difference);
         surf.Canvas.ClipRect(innerRect, ClipOperation.Difference);
-        surf.Canvas.DrawColor(Data.StrokeColor, Data.BlendMode);
+        surf.Canvas.DrawPaintable(Data.Stroke, Data.BlendMode);
     }
     }
 
 
     private void DrawAntiAliased(DrawingSurface surf, RectD rect)
     private void DrawAntiAliased(DrawingSurface surf, RectD rect)
     {
     {
         // draw fill
         // draw fill
-        if (Data.FillColor.A > 0)
+        if (Data.FillPaintable.AnythingVisible)
         {
         {
             int saved = surf.Canvas.Save();
             int saved = surf.Canvas.Save();
 
 
             paint.StrokeWidth = 0;
             paint.StrokeWidth = 0;
-            paint.Color = Data.FillColor;
+            paint.SetPaintable(Data.FillPaintable);
             paint.Style = PaintStyle.Fill;
             paint.Style = PaintStyle.Fill;
             surf.Canvas.DrawRect((float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height, paint);
             surf.Canvas.DrawRect((float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height, paint);
 
 
@@ -87,7 +87,7 @@ internal class RectangleOperation : IMirroredDrawOperation
         // draw stroke
         // draw stroke
         surf.Canvas.Save();
         surf.Canvas.Save();
         paint.StrokeWidth = Data.StrokeWidth > 0 ? Data.StrokeWidth : 1;
         paint.StrokeWidth = Data.StrokeWidth > 0 ? Data.StrokeWidth : 1;
-        paint.Color = Data.StrokeWidth > 0 ? Data.StrokeColor : Data.FillColor;
+        paint.SetPaintable(Data.StrokeWidth > 0 ? Data.Stroke : Data.FillPaintable);
         paint.Style = PaintStyle.Stroke;
         paint.Style = PaintStyle.Stroke;
         RectD innerRect = rect.Inflate(-Data.StrokeWidth / 2f);
         RectD innerRect = rect.Inflate(-Data.StrokeWidth / 2f);
         surf.Canvas.DrawRect((float)innerRect.Left, (float)innerRect.Top, (float)innerRect.Width, (float)innerRect.Height, paint);
         surf.Canvas.DrawRect((float)innerRect.Left, (float)innerRect.Top, (float)innerRect.Width, (float)innerRect.Height, paint);
@@ -96,13 +96,13 @@ internal class RectangleOperation : IMirroredDrawOperation
     public AffectedArea FindAffectedArea(VecI imageSize)
     public AffectedArea FindAffectedArea(VecI imageSize)
     {
     {
         if (Math.Abs(Data.Size.X) < 1 || Math.Abs(Data.Size.Y) < 1 ||
         if (Math.Abs(Data.Size.X) < 1 || Math.Abs(Data.Size.Y) < 1 ||
-            (Data.StrokeColor.A == 0 && Data.FillColor.A == 0))
+            (!Data.Stroke.AnythingVisible && !Data.FillPaintable.AnythingVisible))
             return new();
             return new();
 
 
         RectI affRect = (RectI)new ShapeCorners(Data.Center, Data.Size).AsRotated(Data.Angle, Data.Center).AABBBounds
         RectI affRect = (RectI)new ShapeCorners(Data.Center, Data.Size).AsRotated(Data.Angle, Data.Center).AABBBounds
             .RoundOutwards();
             .RoundOutwards();
 
 
-        if (Data.FillColor.A != 0 || Math.Abs(Data.Size.X) == 1 || Math.Abs(Data.Size.Y) == 1)
+        if (Data.FillPaintable.AnythingVisible || Math.Abs(Data.Size.X) == 1 || Math.Abs(Data.Size.Y) == 1)
             return new(
             return new(
                 OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size.Abs(), Data.Angle,
                 OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size.Abs(), Data.Angle,
                     ChunkPool.FullChunkSize), affRect);
                     ChunkPool.FullChunkSize), affRect);

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 135600d52af3806ab9bf654d783594683bc756e4
+Subproject commit 7d575197477b6803d50e27b0ee0aec7327cc7bd6

+ 2 - 0
src/PixiEditor.ChangeableDocument.Gen/Helpers.cs

@@ -24,6 +24,7 @@ internal static class Helpers
 
 
         StringBuilder sb = new();
         StringBuilder sb = new();
          
          
+        sb.AppendLine("using Drawie.Backend.Core.Numerics;");
         sb.AppendLine("namespace PixiEditor.ChangeableDocument.Actions.Generated;\n");
         sb.AppendLine("namespace PixiEditor.ChangeableDocument.Actions.Generated;\n");
         sb.AppendLine("[System.Runtime.CompilerServices.CompilerGenerated]");
         sb.AppendLine("[System.Runtime.CompilerServices.CompilerGenerated]");
         sb.AppendLine($"public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IMakeChangeAction");
         sb.AppendLine($"public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IMakeChangeAction");
@@ -57,6 +58,7 @@ internal static class Helpers
 
 
         StringBuilder sb = new();
         StringBuilder sb = new();
 
 
+        sb.AppendLine("using Drawie.Backend.Core.Numerics;");
         sb.AppendLine("namespace PixiEditor.ChangeableDocument.Actions.Generated;");
         sb.AppendLine("namespace PixiEditor.ChangeableDocument.Actions.Generated;");
         sb.AppendLine($"public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction" + (isCancelable ? ", PixiEditor.ChangeableDocument.Actions.ICancelableAction" : ""));
         sb.AppendLine($"public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction" + (isCancelable ? ", PixiEditor.ChangeableDocument.Actions.ICancelableAction" : ""));
         sb.AppendLine("{");
         sb.AppendLine("{");

+ 3 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/TransformReferenceLayer_ChangeInfo.cs

@@ -1,3 +1,5 @@
-namespace PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
+using Drawie.Backend.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
 
 
 public record class TransformReferenceLayer_ChangeInfo(ShapeCorners Corners) : IChangeInfo;
 public record class TransformReferenceLayer_ChangeInfo(ShapeCorners Corners) : IChangeInfo;

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyShapeVectorData.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
 using Drawie.Numerics;
@@ -8,9 +9,9 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 public interface IReadOnlyShapeVectorData
 public interface IReadOnlyShapeVectorData
 {
 {
     public Matrix3X3 TransformationMatrix { get; }
     public Matrix3X3 TransformationMatrix { get; }
-    public Color StrokeColor { get; }
+    public Paintable Stroke { get; }
     public bool Fill { get; }
     public bool Fill { get; }
-    public Color FillColor { get; }
+    public Paintable FillPaintable { get; }
     public float StrokeWidth { get; }
     public float StrokeWidth { get; }
     public RectD GeometryAABB { get; }
     public RectD GeometryAABB { get; }
     public RectD TransformedAABB { get; }
     public RectD TransformedAABB { get; }

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyStructureNode.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs

@@ -54,14 +54,14 @@ public class EllipseVectorData : ShapeVectorData, IReadOnlyEllipseData
 
 
         if (Fill)
         if (Fill)
         {
         {
-            shapePaint.Color = FillColor;
+            shapePaint.SetPaintable(FillPaintable);
             shapePaint.Style = PaintStyle.Fill;
             shapePaint.Style = PaintStyle.Fill;
             canvas.DrawOval(Center, Radius, shapePaint);
             canvas.DrawOval(Center, Radius, shapePaint);
         }
         }
 
 
         if (StrokeWidth > 0)
         if (StrokeWidth > 0)
         {
         {
-            shapePaint.Color = StrokeColor;
+            shapePaint.SetPaintable(Stroke);
             shapePaint.Style = PaintStyle.Stroke;
             shapePaint.Style = PaintStyle.Stroke;
             shapePaint.StrokeWidth = StrokeWidth;
             shapePaint.StrokeWidth = StrokeWidth;
             canvas.DrawOval(Center, Radius, shapePaint);
             canvas.DrawOval(Center, Radius, shapePaint);

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/LineVectorData.cs

@@ -54,10 +54,10 @@ public class LineVectorData : ShapeVectorData, IReadOnlyLineData
         .WithMatrix(TransformationMatrix);
         .WithMatrix(TransformationMatrix);
 
 
 
 
-    public LineVectorData(VecD startPos, VecD pos)
+    public LineVectorData(VecD startPos, VecD endPos)
     {
     {
         Start = startPos;
         Start = startPos;
-        End = pos;
+        End = endPos;
         
         
         Fill = false;
         Fill = false;
     }
     }
@@ -83,7 +83,7 @@ public class LineVectorData : ShapeVectorData, IReadOnlyLineData
 
 
         using Paint paint = new Paint() { IsAntiAliased = true };
         using Paint paint = new Paint() { IsAntiAliased = true };
 
 
-        paint.Color = StrokeColor;
+        paint.SetPaintable(Stroke);
         paint.Style = PaintStyle.Stroke;
         paint.Style = PaintStyle.Stroke;
         paint.StrokeWidth = StrokeWidth;
         paint.StrokeWidth = StrokeWidth;
 
 

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs

@@ -59,17 +59,17 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
             IsAntiAliased = true, StrokeJoin = StrokeLineJoin, StrokeCap = StrokeLineCap
             IsAntiAliased = true, StrokeJoin = StrokeLineJoin, StrokeCap = StrokeLineCap
         };
         };
 
 
-        if (Fill && FillColor.A > 0)
+        if (Fill && FillPaintable.AnythingVisible)
         {
         {
-            paint.Color = FillColor;
+            paint.SetPaintable(FillPaintable);
             paint.Style = PaintStyle.Fill;
             paint.Style = PaintStyle.Fill;
 
 
             canvas.DrawPath(Path, paint);
             canvas.DrawPath(Path, paint);
         }
         }
 
 
-        if (StrokeWidth > 0)
+        if (StrokeWidth > 0 && Stroke.AnythingVisible)
         {
         {
-            paint.Color = StrokeColor;
+            paint.SetPaintable(Stroke);
             paint.Style = PaintStyle.Stroke;
             paint.Style = PaintStyle.Stroke;
             paint.StrokeWidth = StrokeWidth;
             paint.StrokeWidth = StrokeWidth;
             
             

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PointsVectorData.cs

@@ -37,7 +37,7 @@ public class PointsVectorData : ShapeVectorData
     private void Rasterize(Canvas canvas, bool applyTransform)
     private void Rasterize(Canvas canvas, bool applyTransform)
     {
     {
         using Paint paint = new Paint();
         using Paint paint = new Paint();
-        paint.Color = FillColor;
+        paint.SetPaintable(FillPaintable);
         paint.StrokeWidth = StrokeWidth;
         paint.StrokeWidth = StrokeWidth;
 
 
         int num = 0;
         int num = 0;

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs

@@ -63,16 +63,16 @@ public class RectangleVectorData : ShapeVectorData, IReadOnlyRectangleData
         using Paint paint = new Paint();
         using Paint paint = new Paint();
         paint.IsAntiAliased = true;
         paint.IsAntiAliased = true;
 
 
-        if (Fill && FillColor.A > 0)
+        if (Fill && FillPaintable.AnythingVisible)
         {
         {
-            paint.Color = FillColor;
+            paint.SetPaintable(FillPaintable);
             paint.Style = PaintStyle.Fill;
             paint.Style = PaintStyle.Fill;
             canvas.DrawRect(RectD.FromCenterAndSize(Center, Size), paint);
             canvas.DrawRect(RectD.FromCenterAndSize(Center, Size), paint);
         }
         }
 
 
-        if (StrokeWidth > 0)
+        if (StrokeWidth > 0 && Stroke.AnythingVisible)
         {
         {
-            paint.Color = StrokeColor;
+            paint.SetPaintable(Stroke);
             paint.Style = PaintStyle.Stroke;
             paint.Style = PaintStyle.Stroke;
 
 
             paint.StrokeWidth = StrokeWidth;
             paint.StrokeWidth = StrokeWidth;

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/ShapeVectorData.cs

@@ -1,6 +1,7 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.Common;
 using PixiEditor.Common;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
@@ -15,8 +16,8 @@ public abstract class ShapeVectorData : ICacheable, ICloneable, IReadOnlyShapeVe
 
 
     public Matrix3X3 TransformationMatrix { get; set; } = Matrix3X3.Identity;
     public Matrix3X3 TransformationMatrix { get; set; } = Matrix3X3.Identity;
 
 
-    public Color StrokeColor { get; set; } = Colors.White;
-    public Color FillColor { get; set; } = Colors.White;
+    public Paintable Stroke { get; set; } = Colors.White;
+    public Paintable FillPaintable { get; set; } = Colors.White;
 
 
     public float StrokeWidth
     public float StrokeWidth
     {
     {
@@ -53,8 +54,8 @@ public abstract class ShapeVectorData : ICacheable, ICloneable, IReadOnlyShapeVe
     {
     {
         HashCode hash = new();
         HashCode hash = new();
         hash.Add(TransformationMatrix);
         hash.Add(TransformationMatrix);
-        hash.Add(StrokeColor);
-        hash.Add(FillColor);
+        hash.Add(Stroke);
+        hash.Add(FillPaintable);
         hash.Add(StrokeWidth);
         hash.Add(StrokeWidth);
         hash.Add(Fill);
         hash.Add(Fill);
         hash.Add(GetSpecificHash());
         hash.Add(GetSpecificHash());

+ 4 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 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.Text;
 using Drawie.Backend.Core.Text;
@@ -173,14 +174,14 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData
         using Paint paint = new Paint() { IsAntiAliased = AntiAlias };
         using Paint paint = new Paint() { IsAntiAliased = AntiAlias };
 
 
         richText.Fill = Fill;
         richText.Fill = Fill;
-        richText.FillColor = FillColor;
-        richText.StrokeColor = StrokeColor;
+        richText.FillPaintable = FillPaintable;
+        richText.StrokePaintable = Stroke;
         richText.StrokeWidth = StrokeWidth;
         richText.StrokeWidth = StrokeWidth;
         richText.Spacing = Spacing;
         richText.Spacing = Spacing;
 
 
         if (MissingFontFamily != null)
         if (MissingFontFamily != null)
         {
         {
-            paint.Color = Fill ? FillColor : StrokeColor;
+            paint.SetPaintable(Fill ? FillPaintable : Stroke);
             canvas.DrawText($"{MissingFontText}: " + MissingFontFamily.Value.Name, Position, Font, paint);
             canvas.DrawText($"{MissingFontText}: " + MissingFontFamily.Value.Name, Position, Font, paint);
         }
         }
         else
         else

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/EllipseNode.cs

@@ -27,7 +27,7 @@ public class EllipseNode : ShapeNode<EllipseVectorData>
     protected override EllipseVectorData? GetShapeData(RenderContext context)
     protected override EllipseVectorData? GetShapeData(RenderContext context)
     {
     {
         return new EllipseVectorData(Center.Value, Radius.Value)
         return new EllipseVectorData(Center.Value, Radius.Value)
-            { StrokeColor = StrokeColor.Value, FillColor = FillColor.Value, StrokeWidth = StrokeWidth.Value };
+            { Stroke = StrokeColor.Value, FillPaintable = FillColor.Value, StrokeWidth = StrokeWidth.Value };
     }
     }
 
 
     public override Node CreateCopy() => new EllipseNode();
     public override Node CreateCopy() => new EllipseNode();

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs

@@ -6,6 +6,7 @@ using PixiEditor.ChangeableDocument.Helpers;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 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;

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs

@@ -226,8 +226,8 @@ internal class CombineStructureMembersOnto_Change : Change
             ShapeVectorData shape = clone as ShapeVectorData;
             ShapeVectorData shape = clone as ShapeVectorData;
             data = new PathVectorData(targetPath)
             data = new PathVectorData(targetPath)
             {
             {
-                StrokeColor = shape.StrokeColor,
-                FillColor = shape.FillColor,
+                Stroke = shape.Stroke,
+                FillPaintable = shape.FillPaintable,
                 StrokeWidth = shape.StrokeWidth,
                 StrokeWidth = shape.StrokeWidth,
                 Fill = shape.Fill,
                 Fill = shape.Fill,
                 TransformationMatrix = shape.TransformationMatrix,
                 TransformationMatrix = shape.TransformationMatrix,

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRasterEllipse_UpdateableChange.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
@@ -8,8 +9,8 @@ internal class DrawRasterEllipse_UpdateableChange : UpdateableChange
     private readonly Guid memberGuid;
     private readonly Guid memberGuid;
     private RectI location;
     private RectI location;
     private double rotation;
     private double rotation;
-    private Color strokeColor;
-    private Color fillColor;
+    private Paintable strokeColor;
+    private Paintable fillColor;
     private float strokeWidth;
     private float strokeWidth;
     private readonly bool drawOnMask;
     private readonly bool drawOnMask;
     private int frame;
     private int frame;
@@ -18,7 +19,7 @@ internal class DrawRasterEllipse_UpdateableChange : UpdateableChange
     private CommittedChunkStorage? storedChunks;
     private CommittedChunkStorage? storedChunks;
 
 
     [GenerateUpdateableChangeActions]
     [GenerateUpdateableChangeActions]
-    public DrawRasterEllipse_UpdateableChange(Guid memberGuid, RectI location, double rotationRad, Color strokeColor, Color fillColor, float strokeWidth, bool antialiased, bool drawOnMask, int frame)
+    public DrawRasterEllipse_UpdateableChange(Guid memberGuid, RectI location, double rotationRad, Paintable strokeColor, Paintable fillColor, float strokeWidth, bool antialiased, bool drawOnMask, int frame)
     {
     {
         this.memberGuid = memberGuid;
         this.memberGuid = memberGuid;
         this.location = location;
         this.location = location;
@@ -32,7 +33,7 @@ internal class DrawRasterEllipse_UpdateableChange : UpdateableChange
     }
     }
 
 
     [UpdateChangeMethod]
     [UpdateChangeMethod]
-    public void Update(RectI location, double rotationRad, Color strokeColor, Color fillColor, float strokeWidth)
+    public void Update(RectI location, double rotationRad, Paintable strokeColor, Paintable fillColor, float strokeWidth)
     {
     {
         this.location = location;
         this.location = location;
         rotation = rotationRad;
         rotation = rotationRad;

+ 10 - 8
src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRasterLine_UpdateableChange.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 using Drawie.Numerics;
@@ -11,7 +12,7 @@ internal class DrawRasterLine_UpdateableChange : UpdateableChange
     private VecD from;
     private VecD from;
     private VecD to;
     private VecD to;
     private float strokeWidth;
     private float strokeWidth;
-    private Color color;
+    private Paintable paintable;
     private StrokeCap caps;
     private StrokeCap caps;
     private readonly bool drawOnMask;
     private readonly bool drawOnMask;
     private CommittedChunkStorage? savedChunks;
     private CommittedChunkStorage? savedChunks;
@@ -21,32 +22,33 @@ internal class DrawRasterLine_UpdateableChange : UpdateableChange
 
 
     [GenerateUpdateableChangeActions]
     [GenerateUpdateableChangeActions]
     public DrawRasterLine_UpdateableChange
     public DrawRasterLine_UpdateableChange
-        (Guid memberGuid, VecD from, VecD to, float strokeWidth, Color color, StrokeCap caps, bool antiAliasing, bool drawOnMask, int frame)
+        (Guid memberGuid, VecD from, VecD to, float strokeWidth, Paintable paintable, StrokeCap caps, bool antiAliasing, bool drawOnMask, int frame)
     {
     {
         this.memberGuid = memberGuid;
         this.memberGuid = memberGuid;
         this.from = from;
         this.from = from;
         this.to = to;
         this.to = to;
         this.strokeWidth = strokeWidth;
         this.strokeWidth = strokeWidth;
-        this.color = color;
+        this.paintable = paintable;
         this.caps = caps;
         this.caps = caps;
         this.drawOnMask = drawOnMask;
         this.drawOnMask = drawOnMask;
         this.frame = frame;
         this.frame = frame;
         this.antiAliasing = antiAliasing;
         this.antiAliasing = antiAliasing;
 
 
-        paint = new Paint() { Color = color, 
+        paint = new Paint() {
             StrokeWidth = strokeWidth, StrokeCap = caps, IsAntiAliased = antiAliasing, BlendMode = BlendMode.SrcOver };
             StrokeWidth = strokeWidth, StrokeCap = caps, IsAntiAliased = antiAliasing, BlendMode = BlendMode.SrcOver };
+        paint.SetPaintable(paintable);
     }
     }
 
 
     [UpdateChangeMethod]
     [UpdateChangeMethod]
-    public void Update(VecD from, VecD to, float strokeWidth, Color color, StrokeCap caps)
+    public void Update(VecD from, VecD to, float strokeWidth, Paintable paintable, StrokeCap caps)
     {
     {
         this.from = from;
         this.from = from;
         this.to = to;
         this.to = to;
-        this.color = color;
+        this.paintable = paintable;
         this.caps = caps;
         this.caps = caps;
         this.strokeWidth = strokeWidth;
         this.strokeWidth = strokeWidth;
         
         
-        paint.Color = color;
+        paint.SetPaintable(paintable);
         paint.StrokeWidth = strokeWidth;
         paint.StrokeWidth = strokeWidth;
         paint.StrokeCap = caps;
         paint.StrokeCap = caps;
     }
     }
@@ -66,7 +68,7 @@ internal class DrawRasterLine_UpdateableChange : UpdateableChange
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
             if (Math.Abs(strokeWidth - 1) < 0.01f && !antiAliasing)
             if (Math.Abs(strokeWidth - 1) < 0.01f && !antiAliasing)
             {
             {
-                image.EnqueueDrawBresenhamLine((VecI)from, (VecI)to, color, BlendMode.SrcOver);
+                image.EnqueueDrawBresenhamLine((VecI)from, (VecI)to, paintable, BlendMode.SrcOver);
             }
             }
             else
             else
             {
             {

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/PreviewShiftLayers_UpdateableChange.cs

@@ -99,8 +99,8 @@ internal class PreviewShiftLayers_UpdateableChange : InterruptableUpdateableChan
                 var newShapeData = new PathVectorData(path)
                 var newShapeData = new PathVectorData(path)
                 {
                 {
                     StrokeWidth = originalShape.StrokeWidth,
                     StrokeWidth = originalShape.StrokeWidth,
-                    StrokeColor = originalShape.StrokeColor,
-                    FillColor = originalShape.FillColor,
+                    Stroke = originalShape.Stroke,
+                    FillPaintable = originalShape.FillPaintable,
                     Fill = originalShape.Fill,
                     Fill = originalShape.Fill,
                     TransformationMatrix = originalShape.TransformationMatrix,
                     TransformationMatrix = originalShape.TransformationMatrix,
                     StrokeLineJoin = join,
                     StrokeLineJoin = join,

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/TransformReferenceLayer_UpdateableChange.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
+using Drawie.Backend.Core.Numerics;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
 
 
 namespace PixiEditor.ChangeableDocument.Changes.Root.ReferenceLayerChanges;
 namespace PixiEditor.ChangeableDocument.Changes.Root.ReferenceLayerChanges;
 
 

+ 3 - 2
src/PixiEditor.ChangeableDocument/Changes/Vectors/ConvertToCurve_Change.cs

@@ -36,11 +36,12 @@ internal class ConvertToCurve_Change : Change
         VectorLayerNode node = target.FindNodeOrThrow<VectorLayerNode>(memberId);
         VectorLayerNode node = target.FindNodeOrThrow<VectorLayerNode>(memberId);
         originalData = node.ShapeData;
         originalData = node.ShapeData;
 
 
+        // TODO: Stroke Line cap and join is missing? Validate
         node.ShapeData = new PathVectorData(originalData.ToPath())
         node.ShapeData = new PathVectorData(originalData.ToPath())
         {
         {
             Fill = originalData.Fill,
             Fill = originalData.Fill,
-            FillColor = originalData.FillColor,
-            StrokeColor = originalData.StrokeColor,
+            FillPaintable = originalData.FillPaintable,
+            Stroke = originalData.Stroke,
             StrokeWidth = originalData.StrokeWidth,
             StrokeWidth = originalData.StrokeWidth,
             TransformationMatrix = originalData.TransformationMatrix
             TransformationMatrix = originalData.TransformationMatrix
         };
         };

+ 1 - 0
src/PixiEditor/Helpers/DocumentViewModelBuilder.cs

@@ -7,6 +7,7 @@ using PixiEditor.ViewModels.Document;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using Drawie.Numerics;
 using Drawie.Numerics;

+ 60 - 5
src/PixiEditor/Helpers/Extensions/ColorHelpers.cs

@@ -1,6 +1,10 @@
-using Avalonia.Media;
+using Avalonia;
+using Avalonia.Media;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Numerics;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using BackendColor = Drawie.Backend.Core.ColorsImpl.Color;
 using BackendColor = Drawie.Backend.Core.ColorsImpl.Color;
+using GradientStop = Drawie.Backend.Core.ColorsImpl.Paintables.GradientStop;
 
 
 namespace PixiEditor.Helpers.Extensions;
 namespace PixiEditor.Helpers.Extensions;
 
 
@@ -16,18 +20,69 @@ internal static class ColorHelpers
     public static Color ToOpaqueMediaColor(this BackendColor color) => Color.FromRgb(color.R, color.G, color.B);
     public static Color ToOpaqueMediaColor(this BackendColor color) => Color.FromRgb(color.R, color.G, color.B);
     public static Color ToColor(this BackendColor color) => Color.FromArgb(color.A, color.R, color.G, color.B);
     public static Color ToColor(this BackendColor color) => Color.FromArgb(color.A, color.R, color.G, color.B);
     public static Color ToMediaColor(this PaletteColor color) => Color.FromRgb(color.R, color.G, color.B);
     public static Color ToMediaColor(this PaletteColor color) => Color.FromRgb(color.R, color.G, color.B);
-    
+
     public static BackendColor BlendColors(BackendColor bottomColor, BackendColor topColor)
     public static BackendColor BlendColors(BackendColor bottomColor, BackendColor topColor)
     {
     {
         if (topColor.A is < 255 and > 0)
         if (topColor.A is < 255 and > 0)
         {
         {
-            byte r = (byte)((topColor.R * topColor.A / 255) + (bottomColor.R * bottomColor.A * (255 - topColor.A) / (255 * 255)));
-            byte g = (byte)((topColor.G * topColor.A / 255) + (bottomColor.G * bottomColor.A * (255 - topColor.A) / (255 * 255)));
-            byte b = (byte)((topColor.B * topColor.A / 255) + (bottomColor.B * bottomColor.A * (255 - topColor.A) / (255 * 255)));
+            byte r = (byte)((topColor.R * topColor.A / 255) +
+                            (bottomColor.R * bottomColor.A * (255 - topColor.A) / (255 * 255)));
+            byte g = (byte)((topColor.G * topColor.A / 255) +
+                            (bottomColor.G * bottomColor.A * (255 - topColor.A) / (255 * 255)));
+            byte b = (byte)((topColor.B * topColor.A / 255) +
+                            (bottomColor.B * bottomColor.A * (255 - topColor.A) / (255 * 255)));
             byte a = (byte)(topColor.A + (bottomColor.A * (255 - topColor.A) / 255));
             byte a = (byte)(topColor.A + (bottomColor.A * (255 - topColor.A) / 255));
             return new BackendColor(r, g, b, a);
             return new BackendColor(r, g, b, a);
         }
         }
 
 
         return topColor.A == 255 ? topColor : bottomColor;
         return topColor.A == 255 ? topColor : bottomColor;
     }
     }
+
+    public static Paintable ToPaintable(this IBrush avaloniaBrush) => avaloniaBrush switch
+    {
+        ISolidColorBrush solidColorBrush => new BackendColor(solidColorBrush.Color.R, solidColorBrush.Color.G,
+            solidColorBrush.Color.B),
+        ILinearGradientBrush linearGradientBrush =>
+            new LinearGradientPaintable(
+                new VecD(linearGradientBrush.StartPoint.Point.X, linearGradientBrush.StartPoint.Point.Y),
+                new VecD(linearGradientBrush.EndPoint.Point.X, linearGradientBrush.EndPoint.Point.Y),
+                linearGradientBrush.GradientStops.Select(stop =>
+                    new GradientStop(new BackendColor(stop.Color.R, stop.Color.G, stop.Color.B), stop.Offset))),
+        IRadialGradientBrush radialGradientBrush => new RadialGradientPaintable(
+            new VecD(radialGradientBrush.Center.Point.X, radialGradientBrush.Center.Point.Y),
+            radialGradientBrush.RadiusX.Scalar,
+            radialGradientBrush.GradientStops.Select(stop =>
+                new GradientStop(new BackendColor(stop.Color.R, stop.Color.G, stop.Color.B), stop.Offset))),
+
+    };
+
+    public static IBrush ToBrush(this Paintable paintable) => paintable switch
+    {
+        ColorPaintable color => new SolidColorBrush(color.Color.ToColor()),
+        LinearGradientPaintable linearGradientPaintable => new LinearGradientBrush
+        {
+            StartPoint = new RelativePoint(linearGradientPaintable.Start.X, linearGradientPaintable.Start.Y, RelativeUnit.Absolute),
+            EndPoint = new RelativePoint(linearGradientPaintable.End.X, linearGradientPaintable.End.Y, RelativeUnit.Absolute),
+            GradientStops = ToAvaloniaGradientStops(linearGradientPaintable.GradientStops)
+        },
+        RadialGradientPaintable radialGradientPaintable => new RadialGradientBrush
+        {
+            Center = new RelativePoint(radialGradientPaintable.Center.X, radialGradientPaintable.Center.Y, RelativeUnit.Absolute),
+            RadiusX = new RelativeScalar(radialGradientPaintable.Radius, RelativeUnit.Absolute),
+            RadiusY = new RelativeScalar(radialGradientPaintable.Radius, RelativeUnit.Absolute),
+            GradientStops = ToAvaloniaGradientStops(radialGradientPaintable.GradientStops)
+        },
+        _ => throw new NotImplementedException()
+    };
+
+    private static GradientStops ToAvaloniaGradientStops(IEnumerable<GradientStop> gradientStops)
+    {
+        GradientStops stops = new GradientStops();
+        foreach (var stop in gradientStops)
+        {
+            stops.Add(new Avalonia.Media.GradientStop(stop.Color.ToColor(), stop.Offset));
+        }
+
+        return stops;
+    }
 }
 }

+ 6 - 5
src/PixiEditor/Helpers/Resources/VectorPathResource.cs

@@ -1,4 +1,5 @@
-using Drawie.Backend.Core.ColorsImpl;
+using Avalonia.Media;
+using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
@@ -12,8 +13,8 @@ public class VectorPathResource
     public string SvgPath { get; set; }
     public string SvgPath { get; set; }
     public StrokeCap StrokeLineCap { get; set; } = StrokeCap.Round;
     public StrokeCap StrokeLineCap { get; set; } = StrokeCap.Round;
     public StrokeJoin StrokeLineJoin { get; set; } = StrokeJoin.Round;
     public StrokeJoin StrokeLineJoin { get; set; } = StrokeJoin.Round;
-    public Color FillColor { get; set; } = Avalonia.Media.Colors.Transparent;
-    public Color StrokeColor { get; set; } = Avalonia.Media.Colors.Black;
+    public IBrush FillColor { get; set; } = new SolidColorBrush(Avalonia.Media.Colors.Transparent);
+    public IBrush StrokeColor { get; set; } = new SolidColorBrush(Avalonia.Media.Colors.Black);
     public float StrokeWidth { get; set; } = 1;
     public float StrokeWidth { get; set; } = 1;
     public PathFillType FillType { get; set; } = PathFillType.Winding;
     public PathFillType FillType { get; set; } = PathFillType.Winding;
     
     
@@ -26,8 +27,8 @@ public class VectorPathResource
         {
         {
             StrokeLineCap = StrokeLineCap,
             StrokeLineCap = StrokeLineCap,
             StrokeLineJoin = StrokeLineJoin,
             StrokeLineJoin = StrokeLineJoin,
-            FillColor = FillColor.ToColor(),
-            StrokeColor = StrokeColor.ToColor(),
+            FillPaintable = FillColor.ToPaintable(),
+            Stroke = StrokeColor.ToPaintable(),
             StrokeWidth = StrokeWidth
             StrokeWidth = StrokeWidth
         };
         };
     }
     }

+ 1 - 0
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -10,6 +10,7 @@ using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Enums;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Extensions.CommonApi.Palettes;

+ 41 - 25
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs

@@ -1,7 +1,10 @@
-using ChunkyImageLib.DataHolders;
+using Avalonia.Media;
+using ChunkyImageLib.DataHolders;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.Actions;
-using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Handlers.Tools;
@@ -10,6 +13,8 @@ using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.Views.Overlays.TransformOverlay;
 using PixiEditor.Views.Overlays.TransformOverlay;
+using Color = Drawie.Backend.Core.ColorsImpl.Color;
+using Colors = Drawie.Backend.Core.ColorsImpl.Colors;
 
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
 
@@ -19,10 +24,10 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 {
 {
     protected double StrokeWidth => toolbar.ToolSize;
     protected double StrokeWidth => toolbar.ToolSize;
 
 
-    protected Color FillColor =>
-        toolbar.Fill ? toolbar.FillColor.ToColor() : Colors.Transparent;
+    protected Paintable FillPaintable =>
+        toolbar.Fill ? toolbar.FillBrush.ToPaintable() : Colors.Transparent;
 
 
-    protected Color StrokeColor => toolbar.StrokeColor.ToColor();
+    protected Paintable StrokePaintable => toolbar.StrokeBrush.ToPaintable();
     protected bool drawOnMask;
     protected bool drawOnMask;
 
 
     protected T? toolViewModel;
     protected T? toolViewModel;
@@ -34,13 +39,13 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     protected IFillableShapeToolbar toolbar;
     protected IFillableShapeToolbar toolbar;
     private IColorsHandler? colorsVM;
     private IColorsHandler? colorsVM;
     private bool ignoreNextColorChange = false;
     private bool ignoreNextColorChange = false;
-    
+
     protected abstract bool UseGlobalUndo { get; }
     protected abstract bool UseGlobalUndo { get; }
     protected abstract bool ShowApplyButton { get; }
     protected abstract bool ShowApplyButton { get; }
 
 
     public override bool CanUndo => !UseGlobalUndo && document.TransformHandler.HasUndo;
     public override bool CanUndo => !UseGlobalUndo && document.TransformHandler.HasUndo;
     public override bool CanRedo => !UseGlobalUndo && document.TransformHandler.HasRedo;
     public override bool CanRedo => !UseGlobalUndo && document.TransformHandler.HasRedo;
-    
+
     public override ExecutionState Start()
     public override ExecutionState Start()
     {
     {
         if (base.Start() == ExecutionState.Error)
         if (base.Start() == ExecutionState.Error)
@@ -57,13 +62,13 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             return ExecutionState.Error;
             return ExecutionState.Error;
         if (!drawOnMask && member is not ILayerHandler)
         if (!drawOnMask && member is not ILayerHandler)
             return ExecutionState.Error;
             return ExecutionState.Error;
-        
+
         if (ActiveMode == ShapeToolMode.Drawing)
         if (ActiveMode == ShapeToolMode.Drawing)
         {
         {
             if (toolbar.SyncWithPrimaryColor)
             if (toolbar.SyncWithPrimaryColor)
             {
             {
-                toolbar.FillColor = colorsVM.PrimaryColor.ToColor();
-                toolbar.StrokeColor = colorsVM.PrimaryColor.ToColor();
+                toolbar.FillBrush = new SolidColorBrush(colorsVM.PrimaryColor.ToColor());
+                toolbar.StrokeBrush = new SolidColorBrush(colorsVM.PrimaryColor.ToColor());
                 ignoreNextColorChange = colorsVM.ColorsTempSwapped;
                 ignoreNextColorChange = colorsVM.ColorsTempSwapped;
             }
             }
 
 
@@ -74,7 +79,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             document.TransformHandler.ShowHandles = false;
             document.TransformHandler.ShowHandles = false;
             document.TransformHandler.IsSizeBoxEnabled = true;
             document.TransformHandler.IsSizeBoxEnabled = true;
             document.TransformHandler.CanAlignToPixels = AlignToPixels;
             document.TransformHandler.CanAlignToPixels = AlignToPixels;
-            
+
             return ExecutionState.Success;
             return ExecutionState.Success;
         }
         }
 
 
@@ -88,12 +93,12 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
                 return ExecutionState.Success;
                 return ExecutionState.Success;
             }
             }
 
 
-            toolbar.StrokeColor = shapeData.StrokeColor.ToColor();
-            toolbar.FillColor = shapeData.FillColor.ToColor();
+            toolbar.StrokeBrush = shapeData.Stroke.ToBrush();
+            toolbar.FillBrush = shapeData.FillPaintable.ToBrush();
             toolbar.ToolSize = shapeData.StrokeWidth;
             toolbar.ToolSize = shapeData.StrokeWidth;
-            toolbar.Fill = shapeData.FillColor != Colors.Transparent;
+            toolbar.Fill = shapeData.FillPaintable is ColorPaintable cp && cp.Color != Colors.Transparent;
             initialCorners = shapeData.TransformationCorners;
             initialCorners = shapeData.TransformationCorners;
-            
+
             ShapeCorners corners = vectorLayerHandler.TransformationCorners;
             ShapeCorners corners = vectorLayerHandler.TransformationCorners;
             document.TransformHandler.ShowTransform(
             document.TransformHandler.ShowTransform(
                 TransformMode, false, corners, false, UseGlobalUndo ? AddToUndo : null);
                 TransformMode, false, corners, false, UseGlobalUndo ? AddToUndo : null);
@@ -155,8 +160,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     {
     {
         var rect = RectD.FromCenterAndSize(corners.RectCenter, corners.RectSize);
         var rect = RectD.FromCenterAndSize(corners.RectCenter, corners.RectSize);
         ShapeData shapeData = new ShapeData(rect.Center, rect.Size, corners.RectRotation, (float)StrokeWidth,
         ShapeData shapeData = new ShapeData(rect.Center, rect.Size, corners.RectRotation, (float)StrokeWidth,
-            StrokeColor,
-            FillColor) { AntiAliasing = toolbar.AntiAliasing };
+            StrokePaintable,
+            FillPaintable) { AntiAliasing = toolbar.AntiAliasing };
         return shapeData;
         return shapeData;
     }
     }
 
 
@@ -168,8 +173,16 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         internals!.ActionAccumulator.AddFinishedActions(EndDrawAction());
         internals!.ActionAccumulator.AddFinishedActions(EndDrawAction());
         document!.TransformHandler.HideTransform();
         document!.TransformHandler.HideTransform();
 
 
-        colorsVM.AddSwatch(StrokeColor.ToPaletteColor());
-        colorsVM.AddSwatch(FillColor.ToPaletteColor());
+        // TODO: Add other paintables support
+        if (StrokePaintable is ColorPaintable strokeColor)
+        {
+            colorsVM.AddSwatch(strokeColor.Color.ToPaletteColor());
+        }
+
+        if (FillPaintable is ColorPaintable fillColor)
+        {
+            colorsVM.AddSwatch(fillColor.Color.ToPaletteColor());
+        }
 
 
         base.OnTransformApplied();
         base.OnTransformApplied();
     }
     }
@@ -188,8 +201,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
 
         ignoreNextColorChange = ActiveMode == ShapeToolMode.Drawing;
         ignoreNextColorChange = ActiveMode == ShapeToolMode.Drawing;
 
 
-        toolbar.StrokeColor = color.ToColor();
-        toolbar.FillColor = color.ToColor();
+        toolbar.StrokeBrush = new SolidColorBrush(color.ToColor());
+        toolbar.FillBrush = new SolidColorBrush(color.ToColor());
     }
     }
 
 
     public override void OnSelectedObjectNudged(VecI distance)
     public override void OnSelectedObjectNudged(VecI distance)
@@ -255,7 +268,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
 
         startDrawingPos = startPos;
         startDrawingPos = startPos;
 
 
-        document!.TransformHandler.ShowTransform(TransformMode, false, new ShapeCorners((RectD)lastRect), false, UseGlobalUndo ? AddToUndo : null);
+        document!.TransformHandler.ShowTransform(TransformMode, false, new ShapeCorners((RectD)lastRect), false,
+            UseGlobalUndo ? AddToUndo : null);
         document.TransformHandler.CanAlignToPixels = AlignToPixels;
         document.TransformHandler.CanAlignToPixels = AlignToPixels;
         document!.TransformHandler.Corners = new ShapeCorners((RectD)lastRect);
         document!.TransformHandler.Corners = new ShapeCorners((RectD)lastRect);
     }
     }
@@ -370,7 +384,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         if (mode == ShapeToolMode.Transform)
         if (mode == ShapeToolMode.Transform)
         {
         {
             document.TransformHandler.HideTransform();
             document.TransformHandler.HideTransform();
-            document!.TransformHandler.ShowTransform(TransformMode, false, initialCorners, ShowApplyButton, UseGlobalUndo ? AddToUndo : null);
+            document!.TransformHandler.ShowTransform(TransformMode, false, initialCorners, ShowApplyButton,
+                UseGlobalUndo ? AddToUndo : null);
             document.TransformHandler.CanAlignToPixels = AlignToPixels;
             document.TransformHandler.CanAlignToPixels = AlignToPixels;
         }
         }
     }
     }
@@ -385,12 +400,13 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     {
     {
         document!.TransformHandler.HideTransform();
         document!.TransformHandler.HideTransform();
     }
     }
-    
+
     private void AddToUndo(ShapeCorners corners)
     private void AddToUndo(ShapeCorners corners)
     {
     {
         if (UseGlobalUndo)
         if (UseGlobalUndo)
         {
         {
-            internals!.ActionAccumulator.AddFinishedActions(EndDrawAction(), TransformMovedAction(ShapeDataFromCorners(corners), corners), EndDrawAction());
+            internals!.ActionAccumulator.AddFinishedActions(EndDrawAction(),
+                TransformMovedAction(ShapeDataFromCorners(corners), corners), EndDrawAction());
         }
         }
     }
     }
 }
 }

+ 1 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/Features/ITransformableExecutor.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;

+ 13 - 8
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs

@@ -1,7 +1,8 @@
-using PixiEditor.ChangeableDocument.Actions;
+using Avalonia.Media;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
-using Drawie.Backend.Core.ColorsImpl;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
@@ -13,6 +14,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.ViewModels.Document.TransformOverlays;
+using Color = Drawie.Backend.Core.ColorsImpl.Color;
 
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
 
@@ -20,7 +22,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
 {
 {
     public override ExecutorType Type => ExecutorType.ToolLinked;
     public override ExecutorType Type => ExecutorType.ToolLinked;
 
 
-    protected Color StrokeColor => toolbar!.StrokeColor.ToColor();
+    protected Paintable StrokePaintable => toolbar!.StrokeBrush.ToPaintable();
     protected double StrokeWidth => toolViewModel!.ToolSize;
     protected double StrokeWidth => toolViewModel!.ToolSize;
     protected abstract bool UseGlobalUndo { get; }
     protected abstract bool UseGlobalUndo { get; }
     protected abstract bool ShowApplyButton { get; }
     protected abstract bool ShowApplyButton { get; }
@@ -64,7 +66,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
         {
         {
             if (toolbar.SyncWithPrimaryColor)
             if (toolbar.SyncWithPrimaryColor)
             {
             {
-                toolbar.StrokeColor = colorsVM.PrimaryColor.ToColor();
+                toolbar.StrokeBrush = new SolidColorBrush(colorsVM.PrimaryColor.ToColor());
                 ignoreNextColorChange = colorsVM.ColorsTempSwapped;
                 ignoreNextColorChange = colorsVM.ColorsTempSwapped;
             }
             }
 
 
@@ -207,7 +209,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
         }
         }
 
 
         ignoreNextColorChange = ActiveMode == ShapeToolMode.Drawing;
         ignoreNextColorChange = ActiveMode == ShapeToolMode.Drawing;
-        toolbar!.StrokeColor = color.ToColor();
+        toolbar!.StrokeBrush = new SolidColorBrush(color.ToColor());
         var colorChangedAction = SettingsChange();
         var colorChangedAction = SettingsChange();
         internals!.ActionAccumulator.AddActions(colorChangedAction);
         internals!.ActionAccumulator.AddActions(colorChangedAction);
     }
     }
@@ -260,7 +262,10 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
         var endDrawAction = EndDraw();
         var endDrawAction = EndDraw();
         internals!.ActionAccumulator.AddFinishedActions(endDrawAction);
         internals!.ActionAccumulator.AddFinishedActions(endDrawAction);
 
 
-        colorsVM.AddSwatch(new PaletteColor(StrokeColor.R, StrokeColor.G, StrokeColor.B));
+        if (StrokePaintable is ColorPaintable colorPaintable)
+        {
+            colorsVM.AddSwatch(new PaletteColor(colorPaintable.Color.R, colorPaintable.Color.G, colorPaintable.Color.B));
+        }
     }
     }
 
 
     public override void ForceStop()
     public override void ForceStop()
@@ -299,12 +304,12 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
 
 
     protected LineVectorData ConstructLineData(VecD start, VecD end)
     protected LineVectorData ConstructLineData(VecD start, VecD end)
     {
     {
-        return new LineVectorData(start, end) { StrokeWidth = (float)StrokeWidth, StrokeColor = StrokeColor };
+        return new LineVectorData(start, end) { StrokeWidth = (float)StrokeWidth, Stroke = StrokePaintable };
     }
     }
     
     
     private void ApplyState(LineVectorData data)
     private void ApplyState(LineVectorData data)
     {
     {
-        toolbar!.StrokeColor = data.StrokeColor.ToColor();
+        toolbar!.StrokeBrush = data.Stroke.ToBrush();
         toolbar!.ToolSize = data.StrokeWidth;
         toolbar!.ToolSize = data.StrokeWidth;
         
         
         document!.LineToolOverlayHandler.Show(data.Start, data.End, ShowApplyButton, AddToUndo);
         document!.LineToolOverlayHandler.Show(data.Start, data.End, ShowApplyButton, AddToUndo);

+ 4 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterEllipseToolExecutor.cs

@@ -24,7 +24,7 @@ internal class RasterEllipseToolExecutor : DrawableShapeToolExecutor<IRasterElli
         lastRect = (RectD)rect;
         lastRect = (RectD)rect;
         lastRadians = rotationRad;
         lastRadians = rotationRad;
 
 
-        internals!.ActionAccumulator.AddActions(new DrawRasterEllipse_Action(memberId, rect, rotationRad, StrokeColor, FillColor, (float)StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable));
+        internals!.ActionAccumulator.AddActions(new DrawRasterEllipse_Action(memberId, rect, rotationRad, StrokePaintable, FillPaintable, (float)StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable));
     }
     }
 
 
     public override ExecutorType Type => ExecutorType.ToolLinked;
     public override ExecutorType Type => ExecutorType.ToolLinked;
@@ -34,7 +34,7 @@ internal class RasterEllipseToolExecutor : DrawableShapeToolExecutor<IRasterElli
     protected override void DrawShape(VecD currentPos, double rotationRad, bool firstDraw) => DrawEllipseOrCircle(currentPos, rotationRad, firstDraw);
     protected override void DrawShape(VecD currentPos, double rotationRad, bool firstDraw) => DrawEllipseOrCircle(currentPos, rotationRad, firstDraw);
     protected override IAction SettingsChangedAction()
     protected override IAction SettingsChangedAction()
     {
     {
-        return new DrawRasterEllipse_Action(memberId, (RectI)lastRect, lastRadians, StrokeColor, FillColor, (float)StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
+        return new DrawRasterEllipse_Action(memberId, (RectI)lastRect, lastRadians, StrokePaintable, FillPaintable, (float)StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
     }
     }
 
 
     protected override IAction TransformMovedAction(ShapeData data, ShapeCorners corners)
     protected override IAction TransformMovedAction(ShapeData data, ShapeCorners corners)
@@ -45,8 +45,8 @@ internal class RasterEllipseToolExecutor : DrawableShapeToolExecutor<IRasterElli
         lastRect = (RectD)rect;
         lastRect = (RectD)rect;
         lastRadians = radians;
         lastRadians = radians;
         
         
-        return new DrawRasterEllipse_Action(memberId, (RectI)lastRect, lastRadians, StrokeColor,
-            FillColor, (float)StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
+        return new DrawRasterEllipse_Action(memberId, (RectI)lastRect, lastRadians, StrokePaintable,
+            FillPaintable, (float)StrokeWidth, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
     }
     }
 
 
     protected override bool CanEditShape(IStructureMemberHandler layer)
     protected override bool CanEditShape(IStructureMemberHandler layer)

+ 3 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterLineToolExecutor.cs

@@ -22,7 +22,7 @@ internal class RasterLineToolExecutor : LineExecutor<ILineToolHandler>
         VecD dir = GetSignedDirection(startDrawingPos, pos);
         VecD dir = GetSignedDirection(startDrawingPos, pos);
         VecD oppositeDir = new VecD(-dir.X, -dir.Y);
         VecD oppositeDir = new VecD(-dir.X, -dir.Y);
         return new DrawRasterLine_Action(memberId, ToPixelPos(startDrawingPos, oppositeDir), ToPixelPos(pos, dir), (float)StrokeWidth,
         return new DrawRasterLine_Action(memberId, ToPixelPos(startDrawingPos, oppositeDir), ToPixelPos(pos, dir), (float)StrokeWidth,
-            StrokeColor, StrokeCap.Butt, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
+            StrokePaintable, StrokeCap.Butt, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
     }
     }
 
 
     protected override IAction TransformOverlayMoved(VecD start, VecD end)
     protected override IAction TransformOverlayMoved(VecD start, VecD end)
@@ -30,7 +30,7 @@ internal class RasterLineToolExecutor : LineExecutor<ILineToolHandler>
         VecD dir = GetSignedDirection(start, end);
         VecD dir = GetSignedDirection(start, end);
         VecD oppositeDir = new VecD(-dir.X, -dir.Y);
         VecD oppositeDir = new VecD(-dir.X, -dir.Y);
         return new DrawRasterLine_Action(memberId, ToPixelPos(start, oppositeDir), ToPixelPos(end, dir), 
         return new DrawRasterLine_Action(memberId, ToPixelPos(start, oppositeDir), ToPixelPos(end, dir), 
-            (float)StrokeWidth, StrokeColor, StrokeCap.Butt, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
+            (float)StrokeWidth, StrokePaintable, StrokeCap.Butt, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable);
     }
     }
 
 
     protected override IAction[] SettingsChange()
     protected override IAction[] SettingsChange()
@@ -38,7 +38,7 @@ internal class RasterLineToolExecutor : LineExecutor<ILineToolHandler>
         VecD dir = GetSignedDirection(startDrawingPos, curPos);
         VecD dir = GetSignedDirection(startDrawingPos, curPos);
         VecD oppositeDir = new VecD(-dir.X, -dir.Y);
         VecD oppositeDir = new VecD(-dir.X, -dir.Y);
         return [new DrawRasterLine_Action(memberId, ToPixelPos(startDrawingPos, oppositeDir), ToPixelPos(curPos, dir), (float)StrokeWidth,
         return [new DrawRasterLine_Action(memberId, ToPixelPos(startDrawingPos, oppositeDir), ToPixelPos(curPos, dir), (float)StrokeWidth,
-            StrokeColor, StrokeCap.Butt, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable)];
+            StrokePaintable, StrokeCap.Butt, toolbar.AntiAliasing, drawOnMask, document!.AnimationHandler.ActiveFrameBindable)];
     }
     }
 
 
     private VecI ToPixelPos(VecD pos, VecD dir)
     private VecI ToPixelPos(VecD pos, VecD dir)

+ 2 - 2
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterRectangleToolExecutor.cs

@@ -26,7 +26,7 @@ internal class RasterRectangleToolExecutor : DrawableShapeToolExecutor<IRasterRe
         lastRect = (RectD)rect;
         lastRect = (RectD)rect;
         lastRadians = rotationRad;
         lastRadians = rotationRad;
 
 
-        lastData = new ShapeData(rect.Center, rect.Size, rotationRad, (float)StrokeWidth, StrokeColor, FillColor)
+        lastData = new ShapeData(rect.Center, rect.Size, rotationRad, (float)StrokeWidth, StrokePaintable, FillPaintable)
         {
         {
             AntiAliasing = toolbar.AntiAliasing
             AntiAliasing = toolbar.AntiAliasing
         };
         };
@@ -43,7 +43,7 @@ internal class RasterRectangleToolExecutor : DrawableShapeToolExecutor<IRasterRe
 
 
     protected override IAction SettingsChangedAction()
     protected override IAction SettingsChangedAction()
     {
     {
-        lastData = new ShapeData(lastData.Center, lastData.Size, lastRadians, (float)StrokeWidth, StrokeColor, FillColor)
+        lastData = new ShapeData(lastData.Center, lastData.Size, lastRadians, (float)StrokeWidth, StrokePaintable, FillPaintable)
         {
         {
             AntiAliasing = toolbar.AntiAliasing
             AntiAliasing = toolbar.AntiAliasing
         };
         };

+ 1 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
+using Drawie.Backend.Core.Numerics;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;

+ 1 - 6
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs

@@ -1,19 +1,14 @@
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
+using System.Collections.Immutable;
 using Avalonia.Input;
 using Avalonia.Input;
-using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
-using PixiEditor.Models.DocumentModels.Public;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.Actions;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels;

+ 5 - 5
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorEllipseToolExecutor.cs

@@ -64,7 +64,7 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
 
 
         EllipseVectorData data = new EllipseVectorData(firstCenter, firstRadius)
         EllipseVectorData data = new EllipseVectorData(firstCenter, firstRadius)
         {
         {
-            StrokeColor = StrokeColor, FillColor = FillColor, StrokeWidth = (float)StrokeWidth,
+            Stroke = StrokePaintable, FillPaintable = FillPaintable, StrokeWidth = (float)StrokeWidth,
         };
         };
 
 
         lastRect = rect;
         lastRect = rect;
@@ -77,8 +77,8 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
         return new SetShapeGeometry_Action(memberId,
         return new SetShapeGeometry_Action(memberId,
             new EllipseVectorData(firstCenter, firstRadius)
             new EllipseVectorData(firstCenter, firstRadius)
             {
             {
-                StrokeColor = StrokeColor,
-                FillColor = FillColor,
+                Stroke = StrokePaintable,
+                FillPaintable = FillPaintable,
                 StrokeWidth = (float)StrokeWidth,
                 StrokeWidth = (float)StrokeWidth,
                 TransformationMatrix = lastMatrix
                 TransformationMatrix = lastMatrix
             });
             });
@@ -108,8 +108,8 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
 
 
         EllipseVectorData ellipseData = new EllipseVectorData(firstCenter, firstRadius)
         EllipseVectorData ellipseData = new EllipseVectorData(firstCenter, firstRadius)
         {
         {
-            StrokeColor = StrokeColor,
-            FillColor = FillColor,
+            Stroke = StrokePaintable,
+            FillPaintable = FillPaintable,
             StrokeWidth = (float)StrokeWidth,
             StrokeWidth = (float)StrokeWidth,
             TransformationMatrix = matrix
             TransformationMatrix = matrix
         };
         };

+ 10 - 10
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs

@@ -92,8 +92,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
 
 
                 if (toolbar.SyncWithPrimaryColor)
                 if (toolbar.SyncWithPrimaryColor)
                 {
                 {
-                    toolbar.StrokeColor = colorHandler.PrimaryColor.ToColor();
-                    toolbar.FillColor = colorHandler.PrimaryColor.ToColor();
+                    toolbar.StrokeBrush = new SolidColorBrush(colorHandler.PrimaryColor.ToColor());
+                    toolbar.FillBrush = new SolidColorBrush(colorHandler.PrimaryColor.ToColor());
                 }
                 }
 
 
                 //below forces undo before starting new path
                 //below forces undo before starting new path
@@ -166,8 +166,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
     {
     {
         if (primary && toolbar.SyncWithPrimaryColor)
         if (primary && toolbar.SyncWithPrimaryColor)
         {
         {
-            toolbar.StrokeColor = color.ToColor();
-            toolbar.FillColor = color.ToColor();
+            toolbar.StrokeBrush = new SolidColorBrush(color.ToColor());
+            toolbar.FillBrush = new SolidColorBrush(color.ToColor());
         }
         }
     }
     }
 
 
@@ -200,8 +200,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
             return new PathVectorData(new VectorPath() { FillType = (PathFillType)vectorPathToolHandler.FillMode })
             return new PathVectorData(new VectorPath() { FillType = (PathFillType)vectorPathToolHandler.FillMode })
             {
             {
                 StrokeWidth = (float)toolbar.ToolSize,
                 StrokeWidth = (float)toolbar.ToolSize,
-                StrokeColor = toolbar.StrokeColor.ToColor(),
-                FillColor = toolbar.Fill ? toolbar.FillColor.ToColor() : Colors.Transparent,
+                Stroke = toolbar.StrokeBrush.ToPaintable(),
+                FillPaintable = toolbar.Fill ? toolbar.FillBrush.ToPaintable() : Colors.Transparent,
                 Fill = toolbar.Fill,
                 Fill = toolbar.Fill,
                 StrokeLineCap = vectorPathToolHandler.StrokeLineCap,
                 StrokeLineCap = vectorPathToolHandler.StrokeLineCap,
                 StrokeLineJoin = vectorPathToolHandler.StrokeLineJoin
                 StrokeLineJoin = vectorPathToolHandler.StrokeLineJoin
@@ -211,8 +211,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
         return new PathVectorData(new VectorPath(path) { FillType = (PathFillType)vectorPathToolHandler.FillMode })
         return new PathVectorData(new VectorPath(path) { FillType = (PathFillType)vectorPathToolHandler.FillMode })
         {
         {
             StrokeWidth = (float)toolbar.ToolSize,
             StrokeWidth = (float)toolbar.ToolSize,
-            StrokeColor = toolbar.StrokeColor.ToColor(),
-            FillColor = toolbar.Fill ? toolbar.FillColor.ToColor() : Colors.Transparent,
+            Stroke = toolbar.StrokeBrush.ToPaintable(),
+            FillPaintable = toolbar.Fill ? toolbar.FillBrush.ToPaintable() : Colors.Transparent,
             Fill = toolbar.Fill,
             Fill = toolbar.Fill,
             StrokeLineCap = vectorPathToolHandler.StrokeLineCap,
             StrokeLineCap = vectorPathToolHandler.StrokeLineCap,
             StrokeLineJoin = vectorPathToolHandler.StrokeLineJoin
             StrokeLineJoin = vectorPathToolHandler.StrokeLineJoin
@@ -260,10 +260,10 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
     private void ApplySettings(PathVectorData pathData)
     private void ApplySettings(PathVectorData pathData)
     {
     {
         toolbar.ToolSize = pathData.StrokeWidth;
         toolbar.ToolSize = pathData.StrokeWidth;
-        toolbar.StrokeColor = pathData.StrokeColor.ToColor();
+        toolbar.StrokeBrush = pathData.Stroke.ToBrush();
         toolbar.ToolSize = pathData.StrokeWidth;
         toolbar.ToolSize = pathData.StrokeWidth;
         toolbar.Fill = pathData.Fill;
         toolbar.Fill = pathData.Fill;
-        toolbar.FillColor = pathData.FillColor.ToColor();
+        toolbar.FillBrush = pathData.FillPaintable.ToBrush();
         toolbar.GetSetting<EnumSettingViewModel<VectorPathFillType>>(nameof(VectorPathToolViewModel.FillMode)).Value = (VectorPathFillType)pathData.Path.FillType;
         toolbar.GetSetting<EnumSettingViewModel<VectorPathFillType>>(nameof(VectorPathToolViewModel.FillMode)).Value = (VectorPathFillType)pathData.Path.FillType;
         toolbar.GetSetting<EnumSettingViewModel<StrokeCap>>(nameof(VectorPathToolViewModel.StrokeLineCap)).Value = pathData.StrokeLineCap;
         toolbar.GetSetting<EnumSettingViewModel<StrokeCap>>(nameof(VectorPathToolViewModel.StrokeLineCap)).Value = pathData.StrokeLineCap;
         toolbar.GetSetting<EnumSettingViewModel<StrokeJoin>>(nameof(VectorPathToolViewModel.StrokeLineJoin)).Value = pathData.StrokeLineJoin;
         toolbar.GetSetting<EnumSettingViewModel<StrokeJoin>>(nameof(VectorPathToolViewModel.StrokeLineJoin)).Value = pathData.StrokeLineJoin;

+ 5 - 5
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorRectangleToolExecutor.cs

@@ -67,7 +67,7 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
 
 
         RectangleVectorData data = new RectangleVectorData(firstCenter, firstSize)
         RectangleVectorData data = new RectangleVectorData(firstCenter, firstSize)
         {
         {
-            StrokeColor = StrokeColor, FillColor = FillColor, StrokeWidth = (float)StrokeWidth,
+            Stroke = StrokePaintable, FillPaintable = FillPaintable, StrokeWidth = (float)StrokeWidth,
         };
         };
 
 
         lastRect = rect;
         lastRect = rect;
@@ -80,8 +80,8 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
         return new SetShapeGeometry_Action(memberId,
         return new SetShapeGeometry_Action(memberId,
             new RectangleVectorData(firstCenter, firstSize)
             new RectangleVectorData(firstCenter, firstSize)
             {
             {
-                StrokeColor = StrokeColor,
-                FillColor = FillColor,
+                Stroke = StrokePaintable,
+                FillPaintable = FillPaintable,
                 StrokeWidth = (float)StrokeWidth,
                 StrokeWidth = (float)StrokeWidth,
                 TransformationMatrix = lastMatrix
                 TransformationMatrix = lastMatrix
             });
             });
@@ -116,8 +116,8 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
 
 
         RectangleVectorData newData = new RectangleVectorData(firstCenter, firstSize)
         RectangleVectorData newData = new RectangleVectorData(firstCenter, firstSize)
         {
         {
-            StrokeColor = data.StrokeColor,
-            FillColor = data.FillColor,
+            Stroke = data.Stroke,
+            FillPaintable = data.FillPaintable,
             StrokeWidth = data.StrokeWidth,
             StrokeWidth = data.StrokeWidth,
             TransformationMatrix = matrix
             TransformationMatrix = matrix
         };
         };

+ 8 - 7
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs

@@ -1,6 +1,6 @@
 using Avalonia.Input;
 using Avalonia.Input;
+using Avalonia.Media;
 using Avalonia.Threading;
 using Avalonia.Threading;
-using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
@@ -16,6 +16,7 @@ using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels;
+using Color = Drawie.Backend.Core.ColorsImpl.Color;
 
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
 
@@ -68,8 +69,8 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
                 textData.TransformationMatrix, textData.Spacing);
                 textData.TransformationMatrix, textData.Spacing);
 
 
             toolbar.Fill = textData.Fill;
             toolbar.Fill = textData.Fill;
-            toolbar.FillColor = textData.FillColor.ToColor();
-            toolbar.StrokeColor = textData.StrokeColor.ToColor();
+            toolbar.FillBrush = textData.FillPaintable.ToBrush();
+            toolbar.StrokeBrush = textData.Stroke.ToBrush();
             toolbar.ToolSize = textData.StrokeWidth;
             toolbar.ToolSize = textData.StrokeWidth;
             toolbar.FontFamily = textData.Font.Family;
             toolbar.FontFamily = textData.Font.Family;
             toolbar.FontSize = textData.Font.Size;
             toolbar.FontSize = textData.Font.Size;
@@ -201,8 +202,8 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
             return;
             return;
         }
         }
 
 
-        toolbar.StrokeColor = color.ToColor();
-        toolbar.FillColor = color.ToColor();
+        toolbar.StrokeBrush = new SolidColorBrush(color.ToColor());
+        toolbar.FillBrush = new SolidColorBrush(color.ToColor());
     }
     }
 
 
     private void TryPutOnPath(VecD pos)
     private void TryPutOnPath(VecD pos)
@@ -255,9 +256,9 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
             Text = text,
             Text = text,
             Position = position,
             Position = position,
             Fill = toolbar.Fill,
             Fill = toolbar.Fill,
-            FillColor = toolbar.FillColor.ToColor(),
+            FillPaintable = toolbar.FillBrush.ToPaintable(),
             StrokeWidth = (float)toolbar.ToolSize,
             StrokeWidth = (float)toolbar.ToolSize,
-            StrokeColor = toolbar.StrokeColor.ToColor(),
+            Stroke = toolbar.StrokeBrush.ToPaintable(),
             TransformationMatrix = lastMatrix,
             TransformationMatrix = lastMatrix,
             Font = cachedFont,
             Font = cachedFont,
             Spacing = toolbar.Spacing,
             Spacing = toolbar.Spacing,

+ 1 - 0
src/PixiEditor/Models/Handlers/IColorsHandler.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Extensions.CommonApi.Palettes;
 
 
 namespace PixiEditor.Models.Handlers;
 namespace PixiEditor.Models.Handlers;

+ 1 - 1
src/PixiEditor/Models/Handlers/Toolbars/IFillableShapeToolbar.cs

@@ -7,5 +7,5 @@ namespace PixiEditor.Models.Handlers.Toolbars;
 internal interface IFillableShapeToolbar : IShapeToolbar
 internal interface IFillableShapeToolbar : IShapeToolbar
 {
 {
     public bool Fill { get; set; }
     public bool Fill { get; set; }
-    public Color FillColor { get; set; }
+    public IBrush FillBrush { get; set; }
 }
 }

+ 1 - 1
src/PixiEditor/Models/Handlers/Toolbars/IShapeToolbar.cs

@@ -4,7 +4,7 @@ namespace PixiEditor.Models.Handlers.Toolbars;
 
 
 internal interface IShapeToolbar : IToolSizeToolbar
 internal interface IShapeToolbar : IToolSizeToolbar
 {
 {
-    public Color StrokeColor { get; set; }
+    public IBrush StrokeBrush { get; set; }
     public bool SyncWithPrimaryColor { get; set; }
     public bool SyncWithPrimaryColor { get; set; }
     public bool AntiAliasing { get; set; }
     public bool AntiAliasing { get; set; }
 }
 }

+ 2 - 2
src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs

@@ -281,7 +281,7 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         {
         {
             var target = styleContext.Fill.Unit;
             var target = styleContext.Fill.Unit;
             float opacity = (float)(styleContext.FillOpacity.Unit?.Value ?? 1);
             float opacity = (float)(styleContext.FillOpacity.Unit?.Value ?? 1);
-            shapeData.FillColor = target.Value.Color.WithAlpha((byte)(Math.Clamp(opacity, 0, 1) * 255));
+            shapeData.FillPaintable = target.Value.Color.WithAlpha((byte)(Math.Clamp(opacity, 0, 1) * 255));
         }
         }
 
 
         if (hasStroke)
         if (hasStroke)
@@ -289,7 +289,7 @@ internal class SvgDocumentBuilder : IDocumentBuilder
             var targetColor = styleContext.Stroke.Unit;
             var targetColor = styleContext.Stroke.Unit;
             var targetWidth = styleContext.StrokeWidth.Unit;
             var targetWidth = styleContext.StrokeWidth.Unit;
 
 
-            shapeData.StrokeColor = targetColor?.Color ?? Colors.Black;
+            shapeData.Stroke = targetColor?.Color ?? Colors.Black;
             shapeData.StrokeWidth = (float)(targetWidth?.PixelsValue ?? 1);
             shapeData.StrokeWidth = (float)(targetWidth?.PixelsValue ?? 1);
         }
         }
 
 

+ 5 - 5
src/PixiEditor/Models/Serialization/Factories/EllipseSerializationFactory.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
@@ -15,9 +16,8 @@ public class EllipseSerializationFactory : VectorShapeSerializationFactory<Ellip
         builder.AddVecD(original.Radius);
         builder.AddVecD(original.Radius);
     }
     }
 
 
-    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
-        bool fill,
-        Color fillColor,
+    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Paintable strokePaintable,
+        bool fill, Paintable fillPaintable,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out EllipseVectorData original)
         out EllipseVectorData original)
     {
     {
@@ -26,9 +26,9 @@ public class EllipseSerializationFactory : VectorShapeSerializationFactory<Ellip
 
 
         original = new EllipseVectorData(center, radius)
         original = new EllipseVectorData(center, radius)
         {
         {
-            StrokeColor = strokeColor,
+            Stroke = strokePaintable,
             Fill = fill,
             Fill = fill,
-            FillColor = fillColor,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             StrokeWidth = strokeWidth,
             TransformationMatrix = matrix
             TransformationMatrix = matrix
         };
         };

+ 5 - 5
src/PixiEditor/Models/Serialization/Factories/LineSerializationFactory.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
@@ -15,9 +16,8 @@ internal class LineSerializationFactory : VectorShapeSerializationFactory<LineVe
         builder.AddVecD(original.End);
         builder.AddVecD(original.End);
     }
     }
 
 
-    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
-        bool fill,
-        Color fillColor,
+    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Paintable strokePaintable,
+        bool fill, Paintable fillPaintable,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out LineVectorData original)
         out LineVectorData original)
     {
     {
@@ -26,8 +26,8 @@ internal class LineSerializationFactory : VectorShapeSerializationFactory<LineVe
 
 
         original = new LineVectorData(start, end)
         original = new LineVectorData(start, end)
         {
         {
-            StrokeColor = strokeColor,
-            FillColor = fillColor,
+            Stroke = strokePaintable,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             StrokeWidth = strokeWidth,
             TransformationMatrix = matrix
             TransformationMatrix = matrix
         };
         };

+ 44 - 0
src/PixiEditor/Models/Serialization/Factories/Paintables/ColorPaintableSerializationFactory.cs

@@ -0,0 +1,44 @@
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+
+namespace PixiEditor.Models.Serialization.Factories.Paintables;
+
+public class ColorPaintableSerializationFactory : SerializationFactory<byte[], ColorPaintable>,
+    IPaintableSerializationFactory
+{
+    public override string DeserializationId { get; } = "PixiEditor.ColorPaintable";
+
+    public override byte[] Serialize(ColorPaintable original)
+    {
+        ByteBuilder builder = new();
+        Serialize(original, builder);
+
+        return builder.Build();
+    }
+
+    public override bool TryDeserialize(object serialized, out ColorPaintable original,
+        (string serializerName, string serializerVersion) serializerData)
+    {
+        if (serialized is not byte[] bytes)
+        {
+            original = null!;
+            return false;
+        }
+
+        ByteExtractor extractor = new(bytes);
+        original = TryDeserialize(extractor) as ColorPaintable;
+
+        return true;
+    }
+
+    public Paintable? TryDeserialize(ByteExtractor extractor)
+    {
+        Color color = extractor.GetColor();
+        return new ColorPaintable(color);
+    }
+
+    public void Serialize(Paintable paintable, ByteBuilder builder)
+    {
+        builder.AddColor((paintable as ColorPaintable).Color);
+    }
+}

+ 9 - 0
src/PixiEditor/Models/Serialization/Factories/Paintables/IPaintableSerializationFactory.cs

@@ -0,0 +1,9 @@
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+
+namespace PixiEditor.Models.Serialization.Factories.Paintables;
+
+public interface IPaintableSerializationFactory
+{
+    public Paintable TryDeserialize(ByteExtractor extractor);
+    public void Serialize(Paintable paintable, ByteBuilder builder);
+}

+ 6 - 5
src/PixiEditor/Models/Serialization/Factories/PointsDataSerializationFactory.cs

@@ -1,6 +1,8 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
 namespace PixiEditor.Models.Serialization.Factories;
 namespace PixiEditor.Models.Serialization.Factories;
@@ -13,17 +15,16 @@ internal class PointsDataSerializationFactory : VectorShapeSerializationFactory<
         builder.AddVecDList(original.Points);
         builder.AddVecDList(original.Points);
     }
     }
 
 
-    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
-        bool fill,
-        Color fillColor,
+    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Paintable strokePaintable,
+        bool fill, Paintable fillPaintable,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out PointsVectorData original)
         out PointsVectorData original)
     {
     {
         List<VecD> points = extractor.GetVecDList();
         List<VecD> points = extractor.GetVecDList();
         original = new PointsVectorData(points)
         original = new PointsVectorData(points)
         {
         {
-            StrokeColor = strokeColor,
-            FillColor = fillColor,
+            Stroke = strokePaintable,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             StrokeWidth = strokeWidth,
             TransformationMatrix = matrix,
             TransformationMatrix = matrix,
             Fill = fill
             Fill = fill

+ 5 - 5
src/PixiEditor/Models/Serialization/Factories/RectangleSerializationFactory.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
@@ -16,9 +17,8 @@ internal class RectangleSerializationFactory : VectorShapeSerializationFactory<R
         builder.AddVecD(original.Size);
         builder.AddVecD(original.Size);
     }
     }
 
 
-    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
-        bool fill,
-        Color fillColor,
+    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Paintable strokePaintable,
+        bool fill, Paintable fillPaintable,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out RectangleVectorData original)
         out RectangleVectorData original)
     {
     {
@@ -27,8 +27,8 @@ internal class RectangleSerializationFactory : VectorShapeSerializationFactory<R
 
 
         original = new RectangleVectorData(center, size)
         original = new RectangleVectorData(center, size)
         {
         {
-            StrokeColor = strokeColor,
-            FillColor = fillColor,
+            Stroke = strokePaintable,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             StrokeWidth = strokeWidth,
             TransformationMatrix = matrix,
             TransformationMatrix = matrix,
             Fill = fill
             Fill = fill

+ 6 - 4
src/PixiEditor/Models/Serialization/Factories/TextSerializationFactory.cs

@@ -1,5 +1,7 @@
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
 using Drawie.Numerics;
@@ -39,8 +41,8 @@ internal class TextSerializationFactory : VectorShapeSerializationFactory<TextVe
         }
         }
     }
     }
 
 
-    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
-        bool fill, Color fillColor,
+    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Paintable strokePaintable,
+        bool fill, Paintable fillPaintable,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out TextVectorData original)
         out TextVectorData original)
     {
     {
@@ -93,9 +95,9 @@ internal class TextSerializationFactory : VectorShapeSerializationFactory<TextVe
         original = new TextVectorData(text)
         original = new TextVectorData(text)
         {
         {
             TransformationMatrix = matrix,
             TransformationMatrix = matrix,
-            StrokeColor = strokeColor,
+            Stroke = strokePaintable,
             Fill = fill,
             Fill = fill,
-            FillColor = fillColor,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             StrokeWidth = strokeWidth,
             Position = position,
             Position = position,
             Font = font,
             Font = font,

+ 6 - 6
src/PixiEditor/Models/Serialization/Factories/VectorPathSerializationFactory.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
 using Drawie.Numerics;
@@ -29,9 +30,8 @@ internal class VectorPathSerializationFactory : VectorShapeSerializationFactory<
         }
         }
     }
     }
 
 
-    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
-        bool fill,
-        Color fillColor,
+    protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Paintable strokePaintable,
+        bool fill, Paintable fillPaintable,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out PathVectorData original)
         out PathVectorData original)
     {
     {
@@ -48,11 +48,11 @@ internal class VectorPathSerializationFactory : VectorShapeSerializationFactory<
 
 
         original = new PathVectorData(path)
         original = new PathVectorData(path)
         {
         {
-            StrokeColor = strokeColor,
-            FillColor = fillColor,
+            Stroke = strokePaintable,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             StrokeWidth = strokeWidth,
             TransformationMatrix = matrix,
             TransformationMatrix = matrix,
-            Fill = fill
+            Fill = fill, // TODO: Check if stroke line cap and join are serialized
         };
         };
 
 
         return true;
         return true;

+ 82 - 17
src/PixiEditor/Models/Serialization/Factories/VectorShapeSerializationFactory.cs

@@ -1,25 +1,48 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using System.Reflection;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
+using PixiEditor.Models.Serialization.Factories.Paintables;
 
 
 namespace PixiEditor.Models.Serialization.Factories;
 namespace PixiEditor.Models.Serialization.Factories;
 
 
 public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<byte[], T> where T : ShapeVectorData
 public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<byte[], T> where T : ShapeVectorData
 {
 {
+    private static List<SerializationFactory> paintableFactories;
+    private static List<SerializationFactory> PaintableFactories => paintableFactories ??= GatherPaintableFactories();
+
+    private static List<SerializationFactory> GatherPaintableFactories()
+    {
+        Assembly assembly = Assembly.GetAssembly(typeof(IPaintableSerializationFactory));
+        Type[] types = assembly.GetTypes();
+        List<SerializationFactory> factories = new();
+
+        foreach (Type type in types)
+        {
+            if (type.IsSubclassOf(typeof(IPaintableSerializationFactory)))
+            {
+                factories.Add((SerializationFactory)Activator.CreateInstance(type));
+            }
+        }
+
+        return factories;
+    }
+
     public override byte[] Serialize(T original)
     public override byte[] Serialize(T original)
     {
     {
         ByteBuilder builder = new ByteBuilder();
         ByteBuilder builder = new ByteBuilder();
         builder.AddMatrix3X3(original.TransformationMatrix);
         builder.AddMatrix3X3(original.TransformationMatrix);
-        builder.AddColor(original.StrokeColor);
+        AddPaintable(original.Stroke, builder);
         builder.AddBool(original.Fill);
         builder.AddBool(original.Fill);
-        builder.AddColor(original.FillColor);
+        AddPaintable(original.FillPaintable, builder);
         builder.AddFloat(original.StrokeWidth);
         builder.AddFloat(original.StrokeWidth);
-        
+
         AddSpecificData(builder, original);
         AddSpecificData(builder, original);
-        
+
         return builder.Build();
         return builder.Build();
     }
     }
-    
+
     protected abstract void AddSpecificData(ByteBuilder builder, T original);
     protected abstract void AddSpecificData(ByteBuilder builder, T original);
 
 
     public override bool TryDeserialize(object serialized, out T original,
     public override bool TryDeserialize(object serialized, out T original,
@@ -30,16 +53,21 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
             original = null;
             original = null;
             return false;
             return false;
         }
         }
-        
+
+        bool fileIsPrePaintables = serializerData.serializerName == "PixiEditor"
+                                   && Version.TryParse(serializerData.serializerVersion, out Version version)
+                                   && version is { Major: 2, Minor: 0, Build: 0, Revision: < 61 };
+
         ByteExtractor extractor = new ByteExtractor(data);
         ByteExtractor extractor = new ByteExtractor(data);
-        
+
         Matrix3X3 matrix = extractor.GetMatrix3X3();
         Matrix3X3 matrix = extractor.GetMatrix3X3();
-        Color strokeColor = extractor.GetColor();
+        Paintable strokeColor = TryGetPaintable(extractor, fileIsPrePaintables);
         bool fill = TryGetBool(extractor, serializerData);
         bool fill = TryGetBool(extractor, serializerData);
-        Color fillColor = extractor.GetColor();
+        Paintable fillColor = TryGetPaintable(extractor, fileIsPrePaintables);
         float strokeWidth;
         float strokeWidth;
         // Previous versions of the serializer saved stroke as int, and serializer data didn't exist
         // Previous versions of the serializer saved stroke as int, and serializer data didn't exist
-        if (string.IsNullOrEmpty(serializerData.serializerVersion) && string.IsNullOrEmpty(serializerData.serializerName))
+        if (string.IsNullOrEmpty(serializerData.serializerVersion) &&
+            string.IsNullOrEmpty(serializerData.serializerName))
         {
         {
             strokeWidth = extractor.GetInt();
             strokeWidth = extractor.GetInt();
         }
         }
@@ -48,12 +76,13 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
             strokeWidth = extractor.GetFloat();
             strokeWidth = extractor.GetFloat();
         }
         }
 
 
-        return DeserializeVectorData(extractor, matrix, strokeColor, fill, fillColor, strokeWidth, serializerData, out original);
+        return DeserializeVectorData(extractor, matrix, strokeColor, fill, fillColor, strokeWidth, serializerData,
+            out original);
     }
     }
-    
-    protected abstract bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
-        bool fill,
-        Color fillColor, float strokeWidth, (string serializerName, string serializerVersion) serializerData,
+
+    protected abstract bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Paintable strokePaintable,
+        bool fill, Paintable fillPaintable, float strokeWidth,
+        (string serializerName, string serializerVersion) serializerData,
         out T original);
         out T original);
 
 
     private bool TryGetBool(ByteExtractor extractor, (string serializerName, string serializerVersion) serializerData)
     private bool TryGetBool(ByteExtractor extractor, (string serializerName, string serializerVersion) serializerData)
@@ -61,7 +90,8 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
         // Previous versions didn't have fill bool
         // Previous versions didn't have fill bool
         if (serializerData.serializerName == "PixiEditor")
         if (serializerData.serializerName == "PixiEditor")
         {
         {
-            if(Version.TryParse(serializerData.serializerVersion, out Version version) && version is { Major: 2, Minor: 0, Build: 0, Revision: < 35 })
+            if (Version.TryParse(serializerData.serializerVersion, out Version version) &&
+                version is { Major: 2, Minor: 0, Build: 0, Revision: < 35 })
             {
             {
                 return true;
                 return true;
             }
             }
@@ -69,4 +99,39 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
 
 
         return extractor.GetBool();
         return extractor.GetBool();
     }
     }
+
+    private Paintable TryGetPaintable(ByteExtractor extractor, bool fileIsPrePaintables)
+    {
+        if (fileIsPrePaintables)
+        {
+            return new ColorPaintable(extractor.GetColor());
+        }
+
+        string paintableType = extractor.GetString();
+        SerializationFactory factory = PaintableFactories.FirstOrDefault(f => f.DeserializationId == paintableType);
+        if (factory == null)
+        {
+            throw new InvalidOperationException($"No factory found for paintable type {paintableType}");
+        }
+
+        factory.Config = Config;
+        factory.ResourceLocator = ResourceLocator;
+
+        return ((IPaintableSerializationFactory)factory).TryDeserialize(extractor);
+    }
+
+    private void AddPaintable(Paintable paintable, ByteBuilder builder)
+    {
+        SerializationFactory factory = PaintableFactories.FirstOrDefault(f => f.OriginalType == paintable.GetType());
+        if (factory == null)
+        {
+            throw new InvalidOperationException($"No factory found for paintable type {paintable.GetType()}");
+        }
+
+        factory.Config = Config;
+        factory.Storage = Storage;
+
+        builder.AddString(factory.DeserializationId);
+        ((IPaintableSerializationFactory)factory).Serialize(paintable, builder);
+    }
 }
 }

+ 18 - 6
src/PixiEditor/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -14,6 +14,7 @@ using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Bridge;
 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.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.ImageData;
@@ -175,11 +176,18 @@ internal partial class DocumentViewModel
             transform = transform.PostConcat(Matrix3X3.CreateScale((float)resizeFactor.X, (float)resizeFactor.Y));
             transform = transform.PostConcat(Matrix3X3.CreateScale((float)resizeFactor.X, (float)resizeFactor.Y));
             primitive.Transform.Unit = new SvgTransformUnit?(new SvgTransformUnit(transform));
             primitive.Transform.Unit = new SvgTransformUnit?(new SvgTransformUnit(transform));
 
 
-            primitive.Fill.Unit = SvgColorUnit.FromRgba(data.FillColor.R, data.FillColor.G,
-                data.FillColor.B, data.Fill ? data.FillColor.A : 0);
+            // TODO: add support for other paintables
+            if (data.FillPaintable is ColorPaintable colorPaintable)
+            {
+                primitive.Fill.Unit = SvgColorUnit.FromRgba(colorPaintable.Color.R, colorPaintable.Color.G,
+                    colorPaintable.Color.B, data.Fill ? colorPaintable.Color.A : 0);
+            }
 
 
-            primitive.Stroke.Unit = SvgColorUnit.FromRgba(data.StrokeColor.R, data.StrokeColor.G,
-                data.StrokeColor.B, data.StrokeColor.A);
+            if (data.Stroke is ColorPaintable strokeColorPaintable)
+            {
+                primitive.Stroke.Unit = SvgColorUnit.FromRgba(strokeColorPaintable.Color.R, strokeColorPaintable.Color.G,
+                    strokeColorPaintable.Color.B, strokeColorPaintable.Color.A);
+            }
 
 
             primitive.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(data.StrokeWidth);
             primitive.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(data.StrokeWidth);
         }
         }
@@ -205,8 +213,12 @@ internal partial class DocumentViewModel
         line.X2.Unit = SvgNumericUnit.FromUserUnits(lineData.End.X);
         line.X2.Unit = SvgNumericUnit.FromUserUnits(lineData.End.X);
         line.Y2.Unit = SvgNumericUnit.FromUserUnits(lineData.End.Y);
         line.Y2.Unit = SvgNumericUnit.FromUserUnits(lineData.End.Y);
 
 
-        line.Stroke.Unit = SvgColorUnit.FromRgba(lineData.StrokeColor.R, lineData.StrokeColor.G,
-            lineData.StrokeColor.B, lineData.StrokeColor.A);
+        if (lineData.Stroke is ColorPaintable colorPaintable)
+        {
+            line.Stroke.Unit = SvgColorUnit.FromRgba(colorPaintable.Color.R, colorPaintable.Color.G,
+                colorPaintable.Color.B, colorPaintable.Color.A);
+        }
+
         line.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(lineData.StrokeWidth);
         line.StrokeWidth.Unit = SvgNumericUnit.FromUserUnits(lineData.StrokeWidth);
 
 
         return line;
         return line;

+ 1 - 0
src/PixiEditor/ViewModels/Document/Nodes/StructureMemberViewModel.cs

@@ -3,6 +3,7 @@ using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Numerics;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;

+ 34 - 3
src/PixiEditor/ViewModels/Tools/ToolSettings/Settings/ColorSettingViewModel.cs

@@ -2,15 +2,46 @@
 
 
 namespace PixiEditor.ViewModels.Tools.ToolSettings.Settings;
 namespace PixiEditor.ViewModels.Tools.ToolSettings.Settings;
 
 
-internal sealed class ColorSettingViewModel : Setting<Color>
+internal sealed class ColorSettingViewModel : Setting<IBrush>
 {
 {
-    public ColorSettingViewModel(string name, string label = "") : this(name, Colors.White, label)
+
+    public IBrush BrushValue
+    {
+        get => base.Value;
+        set
+        {
+            if (base.Value != null && base.Value is GradientBrush oldGradientBrush)
+            {
+                oldGradientBrush.GradientStops.CollectionChanged -= GradientStops_CollectionChanged;
+            }
+
+            base.Value = value;
+
+            if (base.Value is GradientBrush gradientBrush)
+            {
+                gradientBrush.GradientStops.CollectionChanged += GradientStops_CollectionChanged;
+            }
+        }
+    }
+
+    public ColorSettingViewModel(string name, string label = "") : this(name, Brushes.White, label)
     { }
     { }
     
     
-    public ColorSettingViewModel(string name, Color defaultValue, string label = "")
+    public ColorSettingViewModel(string name, IBrush defaultValue, string label = "")
         : base(name)
         : base(name)
     {
     {
         Label = label;
         Label = label;
         Value = defaultValue;
         Value = defaultValue;
+        ValueChanged += OnValueChanged;
+    }
+
+    private void OnValueChanged(object? sender, SettingValueChangedEventArgs<IBrush> e)
+    {
+        OnPropertyChanged(nameof(BrushValue));
+    }
+
+    private void GradientStops_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+    {
+        InvokeValueChanged();
     }
     }
 }
 }

+ 5 - 0
src/PixiEditor/ViewModels/Tools/ToolSettings/Settings/Setting.cs

@@ -84,6 +84,11 @@ internal abstract class Setting : ObservableObject
         }
         }
     }
     }
 
 
+    protected void InvokeValueChanged()
+    {
+        ValueChanged?.Invoke(this, new SettingValueChangedEventArgs<object>(_value, _value));
+    }
+
     public bool IsExposed
     public bool IsExposed
     {
     {
         get => hasOverwrittenExposed ? overwrittenExposed : isExposed;
         get => hasOverwrittenExposed ? overwrittenExposed : isExposed;

+ 4 - 4
src/PixiEditor/ViewModels/Tools/ToolSettings/Toolbars/FillableShapeToolbar.cs

@@ -18,22 +18,22 @@ internal class FillableShapeToolbar : ShapeToolbar, IFillableShapeToolbar
         }
         }
     }
     }
 
 
-    public Color FillColor
+    public IBrush FillBrush
     {
     {
         get
         get
         {
         {
-            return GetSetting<ColorSettingViewModel>(nameof(FillColor)).Value;
+            return GetSetting<ColorSettingViewModel>(nameof(FillBrush)).Value;
         }
         }
         set
         set
         {
         {
-            GetSetting<ColorSettingViewModel>(nameof(FillColor)).Value = value;
+            GetSetting<ColorSettingViewModel>(nameof(FillBrush)).Value = value;
         }
         }
     }
     }
 
 
     public FillableShapeToolbar()
     public FillableShapeToolbar()
     {
     {
         AddSetting(new BoolSettingViewModel(nameof(Fill), "FILL_SHAPE_LABEL") { Value = true });
         AddSetting(new BoolSettingViewModel(nameof(Fill), "FILL_SHAPE_LABEL") { Value = true });
-        AddSetting(new ColorSettingViewModel(nameof(FillColor), "FILL_COLOR_LABEL"));
+        AddSetting(new ColorSettingViewModel(nameof(FillBrush), "FILL_COLOR_LABEL"));
         GetSetting<SizeSettingViewModel>(nameof(ToolSize)).Value = 0;
         GetSetting<SizeSettingViewModel>(nameof(ToolSize)).Value = 0;
     }
     }
 }
 }

+ 4 - 4
src/PixiEditor/ViewModels/Tools/ToolSettings/Toolbars/ShapeToolbar.cs

@@ -19,15 +19,15 @@ internal class ShapeToolbar : Toolbar, IShapeToolbar
         }
         }
     }
     }
 
 
-    public Color StrokeColor
+    public IBrush StrokeBrush
     {
     {
         get
         get
         {
         {
-            return GetSetting<ColorSettingViewModel>(nameof(StrokeColor)).Value;
+            return GetSetting<ColorSettingViewModel>(nameof(StrokeBrush)).Value;
         }
         }
         set
         set
         {
         {
-            GetSetting<ColorSettingViewModel>(nameof(StrokeColor)).Value = value;
+            GetSetting<ColorSettingViewModel>(nameof(StrokeBrush)).Value = value;
         }
         }
     }
     }
 
 
@@ -63,6 +63,6 @@ internal class ShapeToolbar : Toolbar, IShapeToolbar
             new BoolSettingViewModel(nameof(SyncWithPrimaryColor))
             new BoolSettingViewModel(nameof(SyncWithPrimaryColor))
                 { Value = true, Icon = PixiPerfectIcons.LinkedPipette,
                 { Value = true, Icon = PixiPerfectIcons.LinkedPipette,
                     Tooltip = "SYNC_WITH_PRIMARY_COLOR_LABEL"});
                     Tooltip = "SYNC_WITH_PRIMARY_COLOR_LABEL"});
-        AddSetting(new ColorSettingViewModel(nameof(StrokeColor), "STROKE_COLOR_LABEL"));
+        AddSetting(new ColorSettingViewModel(nameof(StrokeBrush), "STROKE_COLOR_LABEL"));
     }
     }
 }
 }

+ 3 - 2
src/PixiEditor/ViewModels/Tools/ToolSettings/Toolbars/ToolbarFactory.cs

@@ -1,7 +1,8 @@
 using System.Reflection;
 using System.Reflection;
+using Avalonia.Media;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Helpers.Extensions;
-using Drawie.Backend.Core.ColorsImpl;
 using PixiEditor.ViewModels.Tools.ToolSettings.Settings;
 using PixiEditor.ViewModels.Tools.ToolSettings.Settings;
+using Colors = Drawie.Backend.Core.ColorsImpl.Colors;
 
 
 namespace PixiEditor.ViewModels.Tools.ToolSettings.Toolbars;
 namespace PixiEditor.ViewModels.Tools.ToolSettings.Toolbars;
 
 
@@ -58,7 +59,7 @@ internal static class ToolbarFactory
         {
         {
             Settings.BoolAttribute => new BoolSettingViewModel(name, (bool)(attribute.DefaultValue ?? false), label),
             Settings.BoolAttribute => new BoolSettingViewModel(name, (bool)(attribute.DefaultValue ?? false), label),
             Settings.ColorAttribute => new ColorSettingViewModel(name,
             Settings.ColorAttribute => new ColorSettingViewModel(name,
-                ((Color)(attribute.DefaultValue ?? Colors.White)).ToColor(), label),
+                ((IBrush)(attribute.DefaultValue ?? Brushes.White)), label),
             Settings.EnumAttribute => GetEnumSetting(propertyType, name, attribute),
             Settings.EnumAttribute => GetEnumSetting(propertyType, name, attribute),
             Settings.PercentAttribute percentAttribute => new PercentSettingViewModel(name, (float)(attribute.DefaultValue ?? 0f), label,
             Settings.PercentAttribute percentAttribute => new PercentSettingViewModel(name, (float)(attribute.DefaultValue ?? 0f), label,
                 percentAttribute.Min, percentAttribute.Max),
                 percentAttribute.Min, percentAttribute.Max),

+ 1 - 1
src/PixiEditor/Views/Input/ToolSettingColorPicker.axaml

@@ -13,5 +13,5 @@
                                      HintColor="{Binding DataContext.ColorsSubViewModel.PrimaryColor,
                                      HintColor="{Binding DataContext.ColorsSubViewModel.PrimaryColor,
         RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type views:MainWindow}, AncestorLevel=1},
         RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type views:MainWindow}, AncestorLevel=1},
         Converter={converters:GenericColorToMediaColorConverter}}"
         Converter={converters:GenericColorToMediaColorConverter}}"
-                                     SelectedColor="{Binding SelectedColor, ElementName=SettingColorPicker, Mode=TwoWay}"/>
+                                     SelectedBrush="{Binding SelectedBrush, ElementName=SettingColorPicker, Mode=TwoWay}"/>
 </UserControl>
 </UserControl>

+ 6 - 6
src/PixiEditor/Views/Input/ToolSettingColorPicker.axaml.cs

@@ -10,14 +10,14 @@ namespace PixiEditor.Views.Input;
 
 
 internal partial class ToolSettingColorPicker : UserControl
 internal partial class ToolSettingColorPicker : UserControl
 {
 {
-    public static readonly StyledProperty<Color> SelectedColorProperty =
-        AvaloniaProperty.Register<ToolSettingColorPicker, Color>(
-            nameof(SelectedColor));
+    public static readonly StyledProperty<IBrush> SelectedBrushProperty =
+        AvaloniaProperty.Register<ToolSettingColorPicker, IBrush>(
+            nameof(SelectedBrush));
 
 
-    public Color SelectedColor
+    public IBrush SelectedBrush
     {
     {
-        get => GetValue(SelectedColorProperty);
-        set => SetValue(SelectedColorProperty, value);
+        get => GetValue(SelectedBrushProperty);
+        set => SetValue(SelectedBrushProperty, value);
     }
     }
 
 
     public ToolSettingColorPicker()
     public ToolSettingColorPicker()

+ 1 - 1
src/PixiEditor/Views/Tools/ToolSettings/Settings/ColorSettingView.axaml

@@ -11,7 +11,7 @@
         <settings:ColorSettingViewModel/>
         <settings:ColorSettingViewModel/>
     </Design.DataContext>
     </Design.DataContext>
     
     
-    <input:ToolSettingColorPicker SelectedColor="{Binding Value, Mode=TwoWay}">
+    <input:ToolSettingColorPicker SelectedBrush="{Binding BrushValue, Mode=TwoWay}">
         <Interaction.Behaviors>
         <Interaction.Behaviors>
             <behaviours:GlobalShortcutFocusBehavior/>
             <behaviours:GlobalShortcutFocusBehavior/>
         </Interaction.Behaviors>
         </Interaction.Behaviors>

+ 1 - 1
src/colorpicker

@@ -1 +1 @@
-Subproject commit e707f2576d2a08600edbecee9d2aa6e3af2fafd2
+Subproject commit 939bc439f1fa48a38b6f7d0c9afc96654566529a

+ 5 - 4
tests/PixiEditor.Backend.Tests/NodeSystemTests.cs

@@ -136,7 +136,8 @@ public class NodeSystemTests
                 if (input.ValueType.IsAssignableTo(typeof(Delegate))) continue;
                 if (input.ValueType.IsAssignableTo(typeof(Delegate))) continue;
                 bool hasFactory = factories.Any(x => x.OriginalType == input.ValueType);
                 bool hasFactory = factories.Any(x => x.OriginalType == input.ValueType);
                 Assert.True(
                 Assert.True(
-                    input.ValueType.IsPrimitive || input.ValueType.IsEnum || input.ValueType == typeof(string) || hasFactory,
+                    input.ValueType.IsPrimitive || input.ValueType.IsEnum || input.ValueType == typeof(string) ||
+                    hasFactory,
                     $"{input.ValueType} doesn't have a factory and is not serializable. Property: {input.InternalPropertyName}, NodeType: {node.GetType().Name}");
                     $"{input.ValueType} doesn't have a factory and is not serializable. Property: {input.InternalPropertyName}, NodeType: {node.GetType().Name}");
             }
             }
         }
         }
@@ -157,13 +158,13 @@ public class NodeSystemTests
                         && x is { IsAbstract: false, IsInterface: false }).ToList();
                         && x is { IsAbstract: false, IsInterface: false }).ToList();
 
 
         List<SerializationFactory> factories = new();
         List<SerializationFactory> factories = new();
-        
+
         foreach (var factoryType in factoryTypes)
         foreach (var factoryType in factoryTypes)
         {
         {
             var factory = (SerializationFactory)Activator.CreateInstance(factoryType);
             var factory = (SerializationFactory)Activator.CreateInstance(factoryType);
             factories.Add(factory);
             factories.Add(factory);
         }
         }
-        
+
         foreach (var type in allVectorTypes)
         foreach (var type in allVectorTypes)
         {
         {
             bool hasFactory = factories.Any(x => x.OriginalType == type);
             bool hasFactory = factories.Any(x => x.OriginalType == type);
@@ -179,7 +180,7 @@ public class NodeSystemTests
                         && x is { IsAbstract: false, IsInterface: false }).ToList();
                         && x is { IsAbstract: false, IsInterface: false }).ToList();
 
 
         List<SerializationFactory> factories = new();
         List<SerializationFactory> factories = new();
-        
+
         foreach (var factoryType in factoryTypes)
         foreach (var factoryType in factoryTypes)
         {
         {
             var factory = (SerializationFactory)Activator.CreateInstance(factoryType);
             var factory = (SerializationFactory)Activator.CreateInstance(factoryType);

+ 47 - 0
tests/PixiEditor.Tests/SerializationTests.cs

@@ -0,0 +1,47 @@
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Surfaces.ImageData;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Changes.NodeGraph;
+using PixiEditor.Models.Serialization;
+using PixiEditor.Models.Serialization.Factories;
+using PixiEditor.Models.Serialization.Factories.Paintables;
+using PixiEditor.Parser.Skia.Encoders;
+
+namespace PixiEditor.Tests;
+
+public class SerializationTests
+{
+    [Fact]
+    public void TestThatAllPaintablesHaveFactories()
+    {
+        var allDrawiePaintableTypes = typeof(Paintable).Assembly.GetTypes()
+            .Where(x => x.IsAssignableTo(typeof(Paintable))
+                        && x is { IsAbstract: false, IsInterface: false }).ToList();
+
+        var pixiEditorAssemblyPaintables = typeof(SerializationFactory).Assembly.GetTypes()
+            .Where(x => x.IsAssignableTo(typeof(Paintable))
+                        && x is { IsAbstract: false, IsInterface: false }).ToList();
+
+        var allPaintables = allDrawiePaintableTypes.Concat(pixiEditorAssemblyPaintables).Distinct().ToList();
+
+        var allFoundFactories = typeof(SerializationFactory).Assembly.GetTypes()
+            .Where(x => x.IsAssignableTo(typeof(IPaintableSerializationFactory))
+                        && x is { IsAbstract: false, IsInterface: false }).ToList();
+
+        List<SerializationFactory> factories = new();
+        QoiEncoder encoder = new QoiEncoder();
+        SerializationConfig config = new SerializationConfig(encoder, ColorSpace.CreateSrgbLinear());
+
+        foreach (var factoryType in allFoundFactories)
+        {
+            var factory = (SerializationFactory)Activator.CreateInstance(factoryType);
+            factories.Add(factory);
+        }
+
+        foreach (var type in allPaintables)
+        {
+            var factory = factories.FirstOrDefault(x => x.OriginalType == type);
+            Assert.NotNull(factory);
+        }
+    }
+}