فهرست منبع

Merge pull request #788 from PixiEditor/gradient-support

Added gradients
Krzysztof Krysiński 4 ماه پیش
والد
کامیت
3205f05768
100فایلهای تغییر یافته به همراه1415 افزوده شده و 650 حذف شده
  1. 3 0
      .gitmodules
  2. 4 3
      src/ChunkyImageLib/ChunkyImage.cs
  3. 0 295
      src/ChunkyImageLib/DataHolders/ShapeCorners.cs
  4. 8 7
      src/ChunkyImageLib/DataHolders/ShapeData.cs
  5. 27 7
      src/ChunkyImageLib/Operations/BresenhamLineOperation.cs
  6. 20 19
      src/ChunkyImageLib/Operations/EllipseOperation.cs
  7. 8 8
      src/ChunkyImageLib/Operations/RectangleOperation.cs
  8. 1 0
      src/ColorPicker
  9. 1 1
      src/Drawie
  10. 2 0
      src/PixiEditor.ChangeableDocument.Gen/Helpers.cs
  11. 3 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/TransformReferenceLayer_ChangeInfo.cs
  12. 3 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyShapeVectorData.cs
  13. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/IReadOnlyStructureNode.cs
  14. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/Shapes/IReadOnlyTextData.cs
  15. 14 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs
  16. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/EllipseVectorData.cs
  17. 3 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/LineVectorData.cs
  18. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs
  19. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PointsVectorData.cs
  20. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs
  21. 5 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/ShapeVectorData.cs
  22. 4 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs
  23. 6 5
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/EllipseNode.cs
  24. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  25. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs
  26. 5 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRasterEllipse_UpdateableChange.cs
  27. 10 8
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRasterLine_UpdateableChange.cs
  28. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/PreviewShiftLayers_UpdateableChange.cs
  29. 26 22
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/ConversionTable.cs
  30. 2 1
      src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/TransformReferenceLayer_UpdateableChange.cs
  31. 3 2
      src/PixiEditor.ChangeableDocument/Changes/Vectors/ConvertToCurve_Change.cs
  32. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll
  33. 33 0
      src/PixiEditor.SVG/DefStorage.cs
  34. 19 0
      src/PixiEditor.SVG/Elements/SvgClipPath.cs
  35. 48 0
      src/PixiEditor.SVG/Elements/SvgDefs.cs
  36. 17 7
      src/PixiEditor.SVG/Elements/SvgGroup.cs
  37. 2 2
      src/PixiEditor.SVG/Elements/SvgImage.cs
  38. 70 0
      src/PixiEditor.SVG/Elements/SvgLinearGradient.cs
  39. 2 2
      src/PixiEditor.SVG/Elements/SvgMask.cs
  40. 4 4
      src/PixiEditor.SVG/Elements/SvgPrimitive.cs
  41. 76 0
      src/PixiEditor.SVG/Elements/SvgRadialGradient.cs
  42. 28 0
      src/PixiEditor.SVG/Elements/SvgStop.cs
  43. 2 2
      src/PixiEditor.SVG/Elements/SvgText.cs
  44. 11 0
      src/PixiEditor.SVG/Enums/SvgRelativityUnit.cs
  45. 8 0
      src/PixiEditor.SVG/Enums/SvgSpreadMethod.cs
  46. 8 0
      src/PixiEditor.SVG/Features/IDefsStorage.cs
  47. 1 1
      src/PixiEditor.SVG/Features/IFillable.cs
  48. 8 0
      src/PixiEditor.SVG/Features/IPaintServer.cs
  49. 1 1
      src/PixiEditor.SVG/Features/IStrokable.cs
  50. 15 8
      src/PixiEditor.SVG/StyleContext.cs
  51. 31 19
      src/PixiEditor.SVG/SvgDocument.cs
  52. 38 13
      src/PixiEditor.SVG/SvgElement.cs
  53. 42 13
      src/PixiEditor.SVG/SvgParser.cs
  54. 3 2
      src/PixiEditor.SVG/Units/SvgColorUnit.cs
  55. 3 2
      src/PixiEditor.SVG/Units/SvgEnumUnit.cs
  56. 2 2
      src/PixiEditor.SVG/Units/SvgLinkUnit.cs
  57. 19 2
      src/PixiEditor.SVG/Units/SvgNumericUnit.cs
  58. 145 0
      src/PixiEditor.SVG/Units/SvgPaintServerUnit.cs
  59. 3 2
      src/PixiEditor.SVG/Units/SvgRectUnit.cs
  60. 5 3
      src/PixiEditor.SVG/Units/SvgStringUnit.cs
  61. 7 5
      src/PixiEditor.SVG/Units/SvgStyleUnit.cs
  62. 4 3
      src/PixiEditor.SVG/Units/SvgTransformUnit.cs
  63. 5 3
      src/PixiEditor.SVG/Units/SvgUnit.cs
  64. 3 0
      src/PixiEditor.UI.Common/Accents/Base.axaml
  65. 62 0
      src/PixiEditor.sln
  66. 1 0
      src/PixiEditor/Helpers/DocumentViewModelBuilder.cs
  67. 73 5
      src/PixiEditor/Helpers/Extensions/ColorHelpers.cs
  68. 6 5
      src/PixiEditor/Helpers/Resources/VectorPathResource.cs
  69. 1 0
      src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs
  70. 41 25
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/DrawableShapeToolExecutor.cs
  71. 1 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/Features/ITransformableExecutor.cs
  72. 13 8
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs
  73. 4 4
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterEllipseToolExecutor.cs
  74. 3 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterLineToolExecutor.cs
  75. 2 2
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/RasterRectangleToolExecutor.cs
  76. 1 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/SimpleShapeToolExecutor.cs
  77. 1 6
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs
  78. 5 5
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorEllipseToolExecutor.cs
  79. 10 10
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorPathToolExecutor.cs
  80. 5 5
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorRectangleToolExecutor.cs
  81. 8 7
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs
  82. 4 1
      src/PixiEditor/Models/Files/PixiFileType.cs
  83. 1 0
      src/PixiEditor/Models/Handlers/IColorsHandler.cs
  84. 1 1
      src/PixiEditor/Models/Handlers/Toolbars/IFillableShapeToolbar.cs
  85. 1 1
      src/PixiEditor/Models/Handlers/Toolbars/IShapeToolbar.cs
  86. 6 4
      src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs
  87. 5 5
      src/PixiEditor/Models/Serialization/Factories/EllipseSerializationFactory.cs
  88. 5 5
      src/PixiEditor/Models/Serialization/Factories/LineSerializationFactory.cs
  89. 44 0
      src/PixiEditor/Models/Serialization/Factories/Paintables/ColorPaintableSerializationFactory.cs
  90. 79 0
      src/PixiEditor/Models/Serialization/Factories/Paintables/GradientPaintableSerializationFactory.cs
  91. 9 0
      src/PixiEditor/Models/Serialization/Factories/Paintables/IPaintableSerializationFactory.cs
  92. 26 0
      src/PixiEditor/Models/Serialization/Factories/Paintables/LinearGradientSerializationFactory.cs
  93. 24 0
      src/PixiEditor/Models/Serialization/Factories/Paintables/RadialGradientSerializationFactory.cs
  94. 24 0
      src/PixiEditor/Models/Serialization/Factories/Paintables/SweepGradientSerializationFactory.cs
  95. 6 5
      src/PixiEditor/Models/Serialization/Factories/PointsDataSerializationFactory.cs
  96. 5 5
      src/PixiEditor/Models/Serialization/Factories/RectangleSerializationFactory.cs
  97. 6 4
      src/PixiEditor/Models/Serialization/Factories/TextSerializationFactory.cs
  98. 6 6
      src/PixiEditor/Models/Serialization/Factories/VectorPathSerializationFactory.cs
  99. 82 17
      src/PixiEditor/Models/Serialization/Factories/VectorShapeSerializationFactory.cs
  100. 5 1
      src/PixiEditor/PixiEditor.csproj

+ 3 - 0
.gitmodules

@@ -7,3 +7,6 @@
 [submodule "src/Drawie"]
 	path = src/Drawie
 	url = https://github.com/PixiEditor/Drawie.git
+[submodule "src/ColorPicker"]
+	path = src/ColorPicker
+	url = https://github.com/PixiEditor/ColorPicker.git

+ 4 - 3
src/ChunkyImageLib/ChunkyImage.cs

@@ -7,6 +7,7 @@ using OneOf.Types;
 using PixiEditor.Common;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 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>
-    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,
         Paint? paint = null)
     {
@@ -737,12 +738,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
     }
 
     /// <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)
         {
             ThrowIfDisposed();
-            BresenhamLineOperation operation = new(from, to, color, blendMode);
+            BresenhamLineOperation operation = new(from, to, paintable, blendMode);
             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.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Numerics;
@@ -7,18 +8,18 @@ namespace ChunkyImageLib.DataHolders;
 
 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;
         Size = size;
         Angle = rotation;
         StrokeWidth = strokeWidth;
         BlendMode = blendMode;
     }
-    public Color StrokeColor { get; }
-    public Color FillColor { get; }
+    public Paintable Stroke { get; }
+    public Paintable FillPaintable { get; }
     public BlendMode BlendMode { get; }
     public VecD Center { get; }
 
@@ -31,8 +32,8 @@ public record struct ShapeData
     
 
     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)
-        => 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);
 
 }

+ 27 - 7
src/ChunkyImageLib/Operations/BresenhamLineOperation.cs

@@ -1,35 +1,52 @@
 using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 
 namespace ChunkyImageLib.Operations;
+
 internal class BresenhamLineOperation : IMirroredDrawOperation
 {
     public bool IgnoreEmptyChunks => false;
     private readonly VecI from;
     private readonly VecI to;
-    private readonly Color color;
+    private readonly Paintable paintable;
     private readonly BlendMode blendMode;
     private readonly VecF[] points;
     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.to = to;
-        this.color = color;
+        this.paintable = paintable.Clone();
+        if (this.paintable is IStartEndPaintable startEndPaintable)
+        {
+            startEndPaintable.Start = from;
+            startEndPaintable.End = to;
+            this.paintable.AbsoluteValues = true;
+        }
+
         this.blendMode = blendMode;
-        paint = new Paint() { BlendMode = blendMode };
+        paint = new Paint() { BlendMode = blendMode, Paintable = paintable };
         points = BresenhamLineHelper.GetBresenhamLine(from, to).Select(v => new VecF(v)).ToArray();
     }
 
     public void DrawOnChunk(Chunk targetChunk, VecI chunkPos)
     {
-        // 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)
+        {
+            // a hacky way to make the lines look slightly better on non full res chunks
+            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;
         surf.Canvas.Save();
@@ -54,16 +71,19 @@ internal class BresenhamLineOperation : IMirroredDrawOperation
             newFrom = (RectI)newFrom.ReflectX((double)verAxisX).Round();
             newTo = (RectI)newTo.ReflectX((double)verAxisX).Round();
         }
+
         if (horAxisY is not null)
         {
             newFrom = (RectI)newFrom.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()
     {
         paint.Dispose();
+        this.paintable.Dispose();
     }
 }

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

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

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

@@ -54,11 +54,11 @@ internal class RectangleOperation : IMirroredDrawOperation
     private void DrawPixelPerfect(DrawingSurface surf, RectD rect, RectD innerRect)
     {
         // draw fill
-        if (Data.FillColor.A > 0)
+        if (Data.FillPaintable.AnythingVisible)
         {
             int saved = surf.Canvas.Save();
             surf.Canvas.ClipRect(innerRect);
-            surf.Canvas.DrawColor(Data.FillColor, Data.BlendMode);
+            surf.Canvas.DrawPaintable(Data.FillPaintable, Data.BlendMode);
             surf.Canvas.RestoreToCount(saved);
         }
 
@@ -66,18 +66,18 @@ internal class RectangleOperation : IMirroredDrawOperation
         surf.Canvas.Save();
         surf.Canvas.ClipRect(rect);
         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)
     {
         // draw fill
-        if (Data.FillColor.A > 0)
+        if (Data.FillPaintable.AnythingVisible)
         {
             int saved = surf.Canvas.Save();
 
             paint.StrokeWidth = 0;
-            paint.Color = Data.FillColor;
+            paint.SetPaintable(Data.FillPaintable);
             paint.Style = PaintStyle.Fill;
             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
         surf.Canvas.Save();
         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;
         RectD innerRect = rect.Inflate(-Data.StrokeWidth / 2f);
         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)
     {
         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();
 
         RectI affRect = (RectI)new ShapeCorners(Data.Center, Data.Size).AsRotated(Data.Angle, Data.Center).AABBBounds
             .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(
                 OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size.Abs(), Data.Angle,
                     ChunkPool.FullChunkSize), affRect);

+ 1 - 0
src/ColorPicker

@@ -0,0 +1 @@
+Subproject commit 79a7ded702389e900dae7f3bd66217c74847b38a

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit e50902ad00a8aa66acdc59d49de181aaf2e37273
+Subproject commit 4a2fdd1c8cd368b24a512033b5935cf3ee758b8f

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

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

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

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

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/Shapes/IReadOnlyTextData.cs

@@ -3,7 +3,7 @@ using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
 
-public interface IReadOnlyTextData
+public interface IReadOnlyTextData : IReadOnlyShapeVectorData
 {
     public string Text { get; }
     public VecD Position { get; }

+ 14 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs

@@ -2,8 +2,10 @@
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 
@@ -16,7 +18,7 @@ public class CreateImageNode : Node, IPreviewRenderable
 
     public InputProperty<VecI> Size { get; }
 
-    public InputProperty<Color> Fill { get; }
+    public InputProperty<Paintable> Fill { get; }
 
     public RenderInputProperty Content { get; }
 
@@ -30,7 +32,7 @@ public class CreateImageNode : Node, IPreviewRenderable
     {
         Output = CreateOutput<Texture>(nameof(Output), "IMAGE", null);
         Size = CreateInput(nameof(Size), "SIZE", new VecI(32, 32)).WithRules(v => v.Min(VecI.One));
-        Fill = CreateInput(nameof(Fill), "FILL", Colors.Transparent);
+        Fill = CreateInput<Paintable>(nameof(Fill), "FILL", new ColorPaintable(Colors.Transparent));
         Content = CreateRenderInput(nameof(Content), "CONTENT");
         ContentOffset = CreateInput(nameof(ContentOffset), "CONTENT_OFFSET", VecD.Zero);
         RenderOutput = CreateRenderOutput("RenderOutput", "RENDER_OUTPUT", () => new Painter(OnPaint));
@@ -54,7 +56,16 @@ public class CreateImageNode : Node, IPreviewRenderable
     {
         var surface = textureCache.RequestTexture(0, Size.Value, context.ProcessingColorSpace, false);
 
-        surface.DrawingSurface.Canvas.Clear(Fill.Value);
+        if (Fill.Value is ColorPaintable colorPaintable)
+        {
+            surface.DrawingSurface.Canvas.Clear(colorPaintable.Color);
+        }
+        else
+        {
+            using Paint paint = new Paint();
+            paint.SetPaintable(Fill.Value);
+            surface.DrawingSurface.Canvas.DrawRect(0, 0, Size.Value.X, Size.Value.Y, paint);
+        }
 
         int saved = surface.DrawingSurface.Canvas.Save();
 

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

@@ -54,14 +54,14 @@ public class EllipseVectorData : ShapeVectorData, IReadOnlyEllipseData
 
         if (Fill)
         {
-            shapePaint.Color = FillColor;
+            shapePaint.SetPaintable(FillPaintable);
             shapePaint.Style = PaintStyle.Fill;
             canvas.DrawOval(Center, Radius, shapePaint);
         }
 
         if (StrokeWidth > 0)
         {
-            shapePaint.Color = StrokeColor;
+            shapePaint.SetPaintable(Stroke);
             shapePaint.Style = PaintStyle.Stroke;
             shapePaint.StrokeWidth = StrokeWidth;
             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);
 
 
-    public LineVectorData(VecD startPos, VecD pos)
+    public LineVectorData(VecD startPos, VecD endPos)
     {
         Start = startPos;
-        End = pos;
+        End = endPos;
         
         Fill = false;
     }
@@ -83,7 +83,7 @@ public class LineVectorData : ShapeVectorData, IReadOnlyLineData
 
         using Paint paint = new Paint() { IsAntiAliased = true };
 
-        paint.Color = StrokeColor;
+        paint.SetPaintable(Stroke);
         paint.Style = PaintStyle.Stroke;
         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
         };
 
-        if (Fill && FillColor.A > 0)
+        if (Fill && FillPaintable.AnythingVisible)
         {
-            paint.Color = FillColor;
+            paint.SetPaintable(FillPaintable);
             paint.Style = PaintStyle.Fill;
 
             canvas.DrawPath(Path, paint);
         }
 
-        if (StrokeWidth > 0)
+        if (StrokeWidth > 0 && Stroke.AnythingVisible)
         {
-            paint.Color = StrokeColor;
+            paint.SetPaintable(Stroke);
             paint.Style = PaintStyle.Stroke;
             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)
     {
         using Paint paint = new Paint();
-        paint.Color = FillColor;
+        paint.SetPaintable(FillPaintable);
         paint.StrokeWidth = StrokeWidth;
 
         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();
         paint.IsAntiAliased = true;
 
-        if (Fill && FillColor.A > 0)
+        if (Fill && FillPaintable.AnythingVisible)
         {
-            paint.Color = FillColor;
+            paint.SetPaintable(FillPaintable);
             paint.Style = PaintStyle.Fill;
             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.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.Common;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 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 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
     {
@@ -53,8 +54,8 @@ public abstract class ShapeVectorData : ICacheable, ICloneable, IReadOnlyShapeVe
     {
         HashCode hash = new();
         hash.Add(TransformationMatrix);
-        hash.Add(StrokeColor);
-        hash.Add(FillColor);
+        hash.Add(Stroke);
+        hash.Add(FillPaintable);
         hash.Add(StrokeWidth);
         hash.Add(Fill);
         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.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Text;
@@ -173,14 +174,14 @@ public class TextVectorData : ShapeVectorData, IReadOnlyTextData
         using Paint paint = new Paint() { IsAntiAliased = AntiAlias };
 
         richText.Fill = Fill;
-        richText.FillColor = FillColor;
-        richText.StrokeColor = StrokeColor;
+        richText.FillPaintable = FillPaintable;
+        richText.StrokePaintable = Stroke;
         richText.StrokeWidth = StrokeWidth;
         richText.Spacing = Spacing;
 
         if (MissingFontFamily != null)
         {
-            paint.Color = Fill ? FillColor : StrokeColor;
+            paint.SetPaintable(Fill ? FillPaintable : Stroke);
             canvas.DrawText($"{MissingFontText}: " + MissingFontFamily.Value.Name, Position, Font, paint);
         }
         else

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

@@ -1,6 +1,7 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes;
@@ -10,8 +11,8 @@ public class EllipseNode : ShapeNode<EllipseVectorData>
 {
     public InputProperty<VecD> Center { get; }
     public InputProperty<VecD> Radius { get; }
-    public InputProperty<Color> StrokeColor { get; }
-    public InputProperty<Color> FillColor { get; }
+    public InputProperty<Paintable> StrokeColor { get; }
+    public InputProperty<Paintable> FillColor { get; }
     public InputProperty<int> StrokeWidth { get; }
 
     public EllipseNode()
@@ -19,15 +20,15 @@ public class EllipseNode : ShapeNode<EllipseVectorData>
         Center = CreateInput<VecD>("Position", "POSITION", VecI.Zero);
         Radius = CreateInput<VecD>("Radius", "RADIUS", new VecD(32, 32)).WithRules(
             v => v.Min(new VecD(1)));
-        StrokeColor = CreateInput<Color>("StrokeColor", "STROKE_COLOR", new Color(0, 0, 0, 255));
-        FillColor = CreateInput<Color>("FillColor", "FILL_COLOR", new Color(0, 0, 0, 255));
+        StrokeColor = CreateInput<Paintable>("StrokeColor", "STROKE_COLOR", new Color(0, 0, 0, 255));
+        FillColor = CreateInput<Paintable>("FillColor", "FILL_COLOR", new Color(0, 0, 0, 255));
         StrokeWidth = CreateInput<int>("StrokeWidth", "STROKE_WIDTH", 1);
     }
 
     protected override EllipseVectorData? GetShapeData(RenderContext context)
     {
         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();

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

@@ -6,6 +6,7 @@ using PixiEditor.ChangeableDocument.Helpers;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
 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;
             data = new PathVectorData(targetPath)
             {
-                StrokeColor = shape.StrokeColor,
-                FillColor = shape.FillColor,
+                Stroke = shape.Stroke,
+                FillPaintable = shape.FillPaintable,
                 StrokeWidth = shape.StrokeWidth,
                 Fill = shape.Fill,
                 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.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 
@@ -8,8 +9,8 @@ internal class DrawRasterEllipse_UpdateableChange : UpdateableChange
     private readonly Guid memberGuid;
     private RectI location;
     private double rotation;
-    private Color strokeColor;
-    private Color fillColor;
+    private Paintable strokeColor;
+    private Paintable fillColor;
     private float strokeWidth;
     private readonly bool drawOnMask;
     private int frame;
@@ -18,7 +19,7 @@ internal class DrawRasterEllipse_UpdateableChange : UpdateableChange
     private CommittedChunkStorage? storedChunks;
 
     [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.location = location;
@@ -32,7 +33,7 @@ internal class DrawRasterEllipse_UpdateableChange : UpdateableChange
     }
 
     [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;
         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.Paintables;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
@@ -11,7 +12,7 @@ internal class DrawRasterLine_UpdateableChange : UpdateableChange
     private VecD from;
     private VecD to;
     private float strokeWidth;
-    private Color color;
+    private Paintable paintable;
     private StrokeCap caps;
     private readonly bool drawOnMask;
     private CommittedChunkStorage? savedChunks;
@@ -21,32 +22,33 @@ internal class DrawRasterLine_UpdateableChange : UpdateableChange
 
     [GenerateUpdateableChangeActions]
     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.from = from;
         this.to = to;
         this.strokeWidth = strokeWidth;
-        this.color = color;
+        this.paintable = paintable;
         this.caps = caps;
         this.drawOnMask = drawOnMask;
         this.frame = frame;
         this.antiAliasing = antiAliasing;
 
-        paint = new Paint() { Color = color, 
+        paint = new Paint() {
             StrokeWidth = strokeWidth, StrokeCap = caps, IsAntiAliased = antiAliasing, BlendMode = BlendMode.SrcOver };
+        paint.SetPaintable(paintable);
     }
 
     [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.to = to;
-        this.color = color;
+        this.paintable = paintable;
         this.caps = caps;
         this.strokeWidth = strokeWidth;
         
-        paint.Color = color;
+        paint.SetPaintable(paintable);
         paint.StrokeWidth = strokeWidth;
         paint.StrokeCap = caps;
     }
@@ -66,7 +68,7 @@ internal class DrawRasterLine_UpdateableChange : UpdateableChange
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
             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
             {

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

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

+ 26 - 22
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/ConversionTable.cs

@@ -1,5 +1,7 @@
 using System.Numerics;
 using System.Reflection;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
 using Drawie.Backend.Core.Shaders.Generation;
 using Drawie.Numerics;
@@ -12,36 +14,38 @@ public static class ConversionTable
         new()
         {
             {
-                typeof(double), 
-                [
+                typeof(double), [
                     (typeof(int), new TypeConverter<double, int>(DoubleToInt)),
                     (typeof(VecD), new TypeConverter<double, VecD>(DoubleToVecD)),
                     (typeof(VecI), new TypeConverter<double, VecI>(DoubleToVecI))
                 ]
             },
             {
-                typeof(int), 
-                [
+                typeof(int), [
                     (typeof(double), new TypeConverter<int, double>(ConvertIntToDouble)),
                     (typeof(VecI), new TypeConverter<int, VecI>(IntToVecI)),
                     (typeof(VecD), new TypeConverter<int, VecD>(IntToVecD)),
                 ]
             },
             {
-                typeof(VecD), 
-                [
+                typeof(VecD), [
                     (typeof(double), new TypeConverter<VecD, double>(VecDToDouble)),
                     (typeof(int), new TypeConverter<VecD, int>(VecDToInt)),
                     (typeof(VecI), new TypeConverter<VecD, VecI>(VecDToVecI)),
                 ]
             },
             {
-                typeof(VecI),
-                [
+                typeof(VecI), [
                     (typeof(double), new TypeConverter<VecI, double>(VecIToDouble)),
                     (typeof(int), new TypeConverter<VecI, int>(VecIToInt)),
                     (typeof(VecD), new TypeConverter<VecI, VecD>(VecIToVecD))
                 ]
+            },
+            {
+                typeof(Color),
+                [
+                    (typeof(Paintable), new TypeConverter<Color, Paintable>(c => new ColorPaintable(c))),
+                ]
             }
         };
 
@@ -66,7 +70,7 @@ public static class ConversionTable
             result = arg;
             return true;
         }
-        
+
         if (_conversionTable.TryGetValue(arg.GetType(), out var converters))
         {
             foreach (var (outType, converter) in converters)
@@ -78,7 +82,7 @@ public static class ConversionTable
                 }
             }
         }
-        
+
         try
         {
             result = System.Convert.ChangeType(arg, targetType);
@@ -90,7 +94,7 @@ public static class ConversionTable
             return false;
         }
     }
-    
+
     public static object Convert(object arg, Type targetType)
     {
         if (TryConvert(arg, targetType, out var result))
@@ -110,52 +114,52 @@ public static class ConversionTable
     {
         return i;
     }
-    
+
     private static double VecDToDouble(VecD vec)
     {
         return vec.X;
-    } 
-    
+    }
+
     private static double VecIToDouble(VecI vecI)
     {
         return vecI.X;
     }
-    
+
     private static VecD DoubleToVecD(double d)
     {
         return new VecD(d, d);
     }
-    
+
     private static VecI DoubleToVecI(double d)
     {
         return new VecI((int)d, (int)d);
     }
-    
+
     private static VecI IntToVecI(int i)
     {
         return new VecI(i, i);
     }
-    
+
     private static VecD IntToVecD(int i)
     {
         return new VecD(i, i);
     }
-    
+
     private static int VecIToInt(VecI vec)
     {
         return vec.X;
     }
-    
+
     private static VecD VecIToVecD(VecI vec)
     {
         return new VecD(vec.X, vec.Y);
     }
-    
+
     private static VecI VecDToVecI(VecD vec)
     {
         return new VecI((int)vec.X, (int)vec.Y);
     }
-    
+
     private static int VecDToInt(VecD vec)
     {
         return (int)vec.X;

+ 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;
 

+ 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);
         originalData = node.ShapeData;
 
+        // TODO: Stroke Line cap and join is missing? Validate
         node.ShapeData = new PathVectorData(originalData.ToPath())
         {
             Fill = originalData.Fill,
-            FillColor = originalData.FillColor,
-            StrokeColor = originalData.StrokeColor,
+            FillPaintable = originalData.FillPaintable,
+            Stroke = originalData.Stroke,
             StrokeWidth = originalData.StrokeWidth,
             TransformationMatrix = originalData.TransformationMatrix
         };

BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll


+ 33 - 0
src/PixiEditor.SVG/DefStorage.cs

@@ -0,0 +1,33 @@
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.SVG;
+
+public class DefStorage
+{
+    public SvgDocument Root { get; }
+    public SvgDefs? Defs { get; private set; }
+
+    private int _idCounter = 0;
+    public DefStorage(SvgDocument root)
+    {
+        Root = root;
+    }
+
+    public void AddDef(SvgElement def)
+    {
+        if (Defs == null)
+        {
+            Defs = new SvgDefs();
+            Root.Defs = Defs;
+        }
+
+        Defs.Children.Add(def);
+        _idCounter++;
+    }
+
+    public string GetNextId()
+    {
+        return _idCounter.ToString();
+    }
+}
+

+ 19 - 0
src/PixiEditor.SVG/Elements/SvgClipPath.cs

@@ -0,0 +1,19 @@
+using System.Xml;
+using PixiEditor.SVG.Enums;
+using PixiEditor.SVG.Features;
+using PixiEditor.SVG.Units;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgClipPath() : SvgElement("clipPath"), IElementContainer
+{
+    public List<SvgElement> Children { get; } = new();
+
+    public SvgProperty<SvgEnumUnit<SvgRelativityUnit>> ClipPathUnits { get; } = new("clipPathUnits");
+
+    public override void ParseData(XmlReader reader, SvgDefs defs)
+    {
+        List<SvgProperty> properties = new List<SvgProperty>() { ClipPathUnits };
+        ParseAttributes(properties, reader, defs);
+    }
+}

+ 48 - 0
src/PixiEditor.SVG/Elements/SvgDefs.cs

@@ -0,0 +1,48 @@
+using System.Xml;
+using PixiEditor.SVG.Features;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgDefs() : SvgElement("defs"), IElementContainer
+{
+    public List<SvgElement> Children { get; } = new();
+
+    public bool TryFindElement(string id, out SvgElement element)
+    {
+        return TryFindElement(Children, id, out element);
+    }
+
+    public override void ParseData(XmlReader reader, SvgDefs defs)
+    {
+
+    }
+
+    private bool TryFindElement(List<SvgElement> root, string id, out SvgElement? element)
+    {
+        if (root == null || root.Count == 0)
+        {
+            element = null;
+            return false;
+        }
+
+        foreach (SvgElement child in root)
+        {
+            if (child.Id.Unit?.Value == id)
+            {
+                element = child;
+                return true;
+            }
+
+            if (child is IElementContainer container && container.Children.Count != 0)
+            {
+                if (TryFindElement(container.Children, id, out element))
+                {
+                    return true;
+                }
+            }
+        }
+
+        element = null;
+        return false;
+    }
+}

+ 17 - 7
src/PixiEditor.SVG/Elements/SvgGroup.cs

@@ -5,22 +5,32 @@ using PixiEditor.SVG.Units;
 
 namespace PixiEditor.SVG.Elements;
 
-public class SvgGroup() : SvgElement("g"), ITransformable, IFillable, IStrokable, IOpacity, IElementContainer
+public class SvgGroup()
+    : SvgElement("g"), ITransformable, IFillable, IStrokable, IOpacity, IElementContainer, IDefsStorage
 {
     public List<SvgElement> Children { get; } = new();
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
-    public SvgProperty<SvgColorUnit> Fill { get; } = new("fill");
+    public SvgProperty<SvgPaintServerUnit> Fill { get; } = new("fill");
     public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
-    public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
+    public SvgProperty<SvgPaintServerUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; } = new("stroke-linecap");
     public SvgProperty<SvgEnumUnit<SvgStrokeLineJoin>> StrokeLineJoin { get; } = new("stroke-linejoin");
     public SvgProperty<SvgNumericUnit> Opacity { get; } = new("opacity");
+    public SvgDefs Defs { get; } = new();
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
-        List<SvgProperty> properties = new List<SvgProperty>() { Transform, Fill, Stroke, StrokeWidth, StrokeLineCap, StrokeLineJoin };
-        ParseAttributes(properties, reader);
-    }
+        List<SvgProperty> properties = new List<SvgProperty>()
+        {
+            Transform,
+            Fill,
+            Stroke,
+            StrokeWidth,
+            StrokeLineCap,
+            StrokeLineJoin
+        };
 
+        ParseAttributes(properties, reader, defs); // TODO: merge with Defs?
+    }
 }

+ 2 - 2
src/PixiEditor.SVG/Elements/SvgImage.cs

@@ -21,9 +21,9 @@ public class SvgImage : SvgElement
         RequiredNamespaces.Add("xlink", "http://www.w3.org/1999/xlink");
     }
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
         List<SvgProperty> properties = new List<SvgProperty>() { X, Y, Width, Height, Href, Mask, ImageRendering };
-        ParseAttributes(properties, reader);
+        ParseAttributes(properties, reader, defs);
     }
 }

+ 70 - 0
src/PixiEditor.SVG/Elements/SvgLinearGradient.cs

@@ -0,0 +1,70 @@
+using System.Xml;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
+using PixiEditor.SVG.Enums;
+using PixiEditor.SVG.Features;
+using PixiEditor.SVG.Units;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgLinearGradient() : SvgElement("linearGradient"), IElementContainer, IPaintServer
+{
+    public List<SvgElement> Children { get; } = new();
+    public SvgProperty<SvgTransformUnit> GradientTransform { get; } = new("gradientTransform");
+    public SvgProperty<SvgNumericUnit> X1 { get; } = new("x1");
+    public SvgProperty<SvgNumericUnit> Y1 { get; } = new("y1");
+    public SvgProperty<SvgNumericUnit> X2 { get; } = new("x2");
+    public SvgProperty<SvgNumericUnit> Y2 { get; } = new("y2");
+    public SvgProperty<SvgEnumUnit<SvgSpreadMethod>> SpreadMethod { get; } = new("spreadMethod");
+    public SvgProperty<SvgEnumUnit<SvgRelativityUnit>> GradientUnits { get; } = new("gradientUnits");
+
+    public override void ParseData(XmlReader reader, SvgDefs defs)
+    {
+        List<SvgProperty> properties = GetProperties().ToList();
+
+        do
+        {
+            ParseAttributes(properties, reader, defs);
+        } while (reader.MoveToNextAttribute());
+    }
+
+    protected IEnumerable<SvgProperty> GetProperties()
+    {
+        yield return GradientTransform;
+        yield return X1;
+        yield return Y1;
+        yield return X2;
+        yield return Y2;
+        yield return SpreadMethod;
+        yield return GradientUnits;
+    }
+
+    public Paintable GetPaintable()
+    {
+        VecD start = new VecD(GetUnit(X1)?.Value ?? 0, GetUnit(Y1)?.Value ?? 0.5);
+        VecD end = new VecD(GetUnit(X2)?.Value ?? 1, GetUnit(Y2)?.Value ?? 0.5);
+
+        List<GradientStop> gradientStops = new();
+        foreach (SvgElement child in Children)
+        {
+            if (child is SvgStop stop)
+            {
+                Color color = stop.GetUnit(stop.StopColor)?.Color ?? Colors.Black;
+                color = color.WithAlpha((byte)((stop.StopOpacity.Unit?.NormalizedValue() ?? 1) * 255));
+                gradientStops.Add(
+                    new GradientStop(color, GetUnit(stop.Offset)?.NormalizedValue() ?? 0));
+            }
+        }
+
+        var unit = GetUnit(GradientUnits)?.Value ?? SvgRelativityUnit.ObjectBoundingBox;
+        var transform = GetUnit(GradientTransform)?.MatrixValue ?? Matrix3X3.Identity;
+
+        return new LinearGradientPaintable(start, end, gradientStops)
+        {
+            AbsoluteValues = unit == SvgRelativityUnit.UserSpaceOnUse,
+            Transform = transform
+        };
+    }
+}

+ 2 - 2
src/PixiEditor.SVG/Elements/SvgMask.cs

@@ -13,9 +13,9 @@ public class SvgMask() : SvgElement("mask"), IElementContainer
     public SvgProperty<SvgNumericUnit> Height { get; } = new("height");
     public List<SvgElement> Children { get; } = new();
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
         List<SvgProperty> properties = new List<SvgProperty>() { X, Y, Width, Height };
-        ParseAttributes(properties, reader);
+        ParseAttributes(properties, reader, defs);
     }
 }

+ 4 - 4
src/PixiEditor.SVG/Elements/SvgPrimitive.cs

@@ -8,9 +8,9 @@ namespace PixiEditor.SVG.Elements;
 public abstract class SvgPrimitive(string tagName) : SvgElement(tagName), ITransformable, IFillable, IStrokable, IOpacity
 {
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
-    public SvgProperty<SvgColorUnit> Fill { get; } = new("fill");
+    public SvgProperty<SvgPaintServerUnit> Fill { get; } = new("fill");
     public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
-    public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
+    public SvgProperty<SvgPaintServerUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
     
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; } = new("stroke-linecap");
@@ -19,7 +19,7 @@ public abstract class SvgPrimitive(string tagName) : SvgElement(tagName), ITrans
 
     public SvgProperty<SvgNumericUnit> Opacity { get; } = new("opacity");
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
         List<SvgProperty> properties = GetProperties().ToList();
         
@@ -34,7 +34,7 @@ public abstract class SvgPrimitive(string tagName) : SvgElement(tagName), ITrans
 
         do
         {
-            ParseAttributes(properties, reader);
+            ParseAttributes(properties, reader, defs);
         } while (reader.MoveToNextAttribute());
     }
 

+ 76 - 0
src/PixiEditor.SVG/Elements/SvgRadialGradient.cs

@@ -0,0 +1,76 @@
+using System.Xml;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
+using PixiEditor.SVG.Enums;
+using PixiEditor.SVG.Features;
+using PixiEditor.SVG.Units;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgRadialGradient() : SvgElement("radialGradient"), IElementContainer, IPaintServer
+{
+    public List<SvgElement> Children { get; } = new();
+    public SvgProperty<SvgTransformUnit> GradientTransform { get; } = new("gradientTransform");
+    public SvgProperty<SvgNumericUnit> Cx { get; } = new("cx");
+    public SvgProperty<SvgNumericUnit> Cy { get; } = new("cy");
+    public SvgProperty<SvgNumericUnit> R { get; } = new("r");
+    public SvgProperty<SvgNumericUnit> Fx { get; } = new("fx");
+    public SvgProperty<SvgNumericUnit> Fy { get; } = new("fy");
+    public SvgProperty<SvgEnumUnit<SvgSpreadMethod>> SpreadMethod { get; } = new("spreadMethod");
+    public SvgProperty<SvgEnumUnit<SvgRelativityUnit>> GradientUnits { get; } = new("gradientUnits");
+
+    public override void ParseData(XmlReader reader, SvgDefs defs)
+    {
+        List<SvgProperty> properties = GetProperties().ToList();
+
+        do
+        {
+            ParseAttributes(properties, reader, defs);
+        } while (reader.MoveToNextAttribute());
+    }
+
+    protected IEnumerable<SvgProperty> GetProperties()
+    {
+        yield return GradientTransform;
+        yield return Cx;
+        yield return Cy;
+        yield return R;
+        yield return Fx;
+        yield return Fy;
+        yield return SpreadMethod;
+        yield return GradientUnits;
+    }
+
+    public Paintable GetPaintable()
+    {
+        VecD center = new VecD(GetUnit(Cx)?.Value ?? 0.5, GetUnit(Cy)?.Value ?? 0.5);
+        //VecD focus = new VecD(Fx.Unit.Value.PixelsValue ?? 0, Fy.Unit.Value.PixelsValue ?? 0);
+        double radius = GetUnit(R)?.Value ?? 0.5;
+
+        List<GradientStop> gradientStops = new();
+        foreach (SvgElement child in Children)
+        {
+            if (child is SvgStop stop)
+            {
+                Color color = stop.GetUnit(stop.StopColor)?.Color ?? Colors.Black;
+                color = color.WithAlpha((byte)((stop.StopOpacity.Unit?.NormalizedValue() ?? 1) * 255));
+                gradientStops.Add(
+                    new GradientStop(color, stop.GetUnit(stop.Offset)?.NormalizedValue() ?? 0));
+            }
+        }
+
+        var unit = GetUnit(GradientUnits)?.Value ?? SvgRelativityUnit.ObjectBoundingBox;
+        var transform = GetUnit(GradientTransform)?.MatrixValue ?? Matrix3X3.Identity;
+
+        RadialGradientPaintable radialGradientPaintable =
+            new(center, radius, gradientStops)
+            {
+                AbsoluteValues = unit == SvgRelativityUnit.UserSpaceOnUse,
+                Transform = transform
+            };
+
+        return radialGradientPaintable;
+    }
+}

+ 28 - 0
src/PixiEditor.SVG/Elements/SvgStop.cs

@@ -0,0 +1,28 @@
+using System.Xml;
+using PixiEditor.SVG.Units;
+
+namespace PixiEditor.SVG.Elements;
+
+public class SvgStop() : SvgElement("stop")
+{
+    public SvgProperty<SvgColorUnit> StopColor { get; } = new("stop-color");
+    public SvgProperty<SvgNumericUnit> Offset { get; } = new("offset");
+    public SvgProperty<SvgNumericUnit> StopOpacity { get; } = new("stop-opacity");
+
+    public override void ParseData(XmlReader reader, SvgDefs defs)
+    {
+        List<SvgProperty> properties = GetProperties().ToList();
+
+        do
+        {
+            ParseAttributes(properties, reader, defs);
+        } while (reader.MoveToNextAttribute());
+    }
+
+    private IEnumerable<SvgProperty> GetProperties()
+    {
+        yield return StopColor;
+        yield return Offset;
+        yield return StopOpacity;
+    }
+}

+ 2 - 2
src/PixiEditor.SVG/Elements/SvgText.cs

@@ -14,9 +14,9 @@ public class SvgText() : SvgPrimitive("text")
     public SvgProperty<SvgEnumUnit<SvgFontWeight>> FontWeight { get; } = new("font-weight");
     public SvgProperty<SvgEnumUnit<SvgFontStyle>> FontStyle { get; } = new("font-style");
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
-        base.ParseData(reader);
+        base.ParseData(reader, defs);
         Text.Unit = new SvgStringUnit(ParseContent(reader));
     }
 

+ 11 - 0
src/PixiEditor.SVG/Enums/SvgRelativityUnit.cs

@@ -0,0 +1,11 @@
+using PixiEditor.SVG.Attributes;
+
+namespace PixiEditor.SVG.Enums;
+
+public enum SvgRelativityUnit
+{
+    [SvgValue("userSpaceOnUse")]
+    UserSpaceOnUse,
+    [SvgValue("objectBoundingBox")]
+    ObjectBoundingBox
+}

+ 8 - 0
src/PixiEditor.SVG/Enums/SvgSpreadMethod.cs

@@ -0,0 +1,8 @@
+namespace PixiEditor.SVG.Enums;
+
+public enum SvgSpreadMethod
+{
+    Pad,
+    Reflect,
+    Repeat
+}

+ 8 - 0
src/PixiEditor.SVG/Features/IDefsStorage.cs

@@ -0,0 +1,8 @@
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.SVG.Features;
+
+public interface IDefsStorage
+{
+    public SvgDefs Defs { get; }
+}

+ 1 - 1
src/PixiEditor.SVG/Features/IFillable.cs

@@ -5,6 +5,6 @@ namespace PixiEditor.SVG.Features;
 
 public interface IFillable
 {
-    public SvgProperty<SvgColorUnit> Fill { get; }
+    public SvgProperty<SvgPaintServerUnit> Fill { get; }
     public SvgProperty<SvgNumericUnit> FillOpacity { get; }
 }

+ 8 - 0
src/PixiEditor.SVG/Features/IPaintServer.cs

@@ -0,0 +1,8 @@
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+
+namespace PixiEditor.SVG.Features;
+
+public interface IPaintServer
+{
+    public Paintable GetPaintable();
+}

+ 1 - 1
src/PixiEditor.SVG/Features/IStrokable.cs

@@ -5,7 +5,7 @@ namespace PixiEditor.SVG.Features;
 
 public interface IStrokable
 {
-    public SvgProperty<SvgColorUnit> Stroke { get; }
+    public SvgProperty<SvgPaintServerUnit> Stroke { get; }
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineJoin>> StrokeLineJoin { get; }

+ 15 - 8
src/PixiEditor.SVG/StyleContext.cs

@@ -1,5 +1,7 @@
-using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
+using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Enums;
 using PixiEditor.SVG.Features;
 using PixiEditor.SVG.Units;
@@ -9,8 +11,8 @@ namespace PixiEditor.SVG;
 public struct StyleContext
 {
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; }
-    public SvgProperty<SvgColorUnit> Stroke { get; }
-    public SvgProperty<SvgColorUnit> Fill { get; }
+    public SvgProperty<SvgPaintServerUnit> Stroke { get; }
+    public SvgProperty<SvgPaintServerUnit> Fill { get; }
     public SvgProperty<SvgNumericUnit> FillOpacity { get; }
     public SvgProperty<SvgTransformUnit> Transform { get; }
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; }
@@ -18,6 +20,7 @@ public struct StyleContext
     public SvgProperty<SvgNumericUnit> Opacity { get; }
     public SvgProperty<SvgStyleUnit> InlineStyle { get; set; }
     public VecD ViewboxOrigin { get; set; }
+    public SvgDefs Defs { get; set; }
 
     public StyleContext()
     {
@@ -25,19 +28,20 @@ public struct StyleContext
         Stroke = new("stroke");
         Fill = new("fill");
         FillOpacity = new("fill-opacity");
-        Fill.Unit = new SvgColorUnit?(new SvgColorUnit("black"));
+        Fill.Unit = SvgPaintServerUnit.FromColor(Colors.Black);
         Transform = new("transform");
         StrokeLineCap = new("stroke-linecap");
         StrokeLineJoin = new("stroke-linejoin");
         Opacity = new("opacity");
         InlineStyle = new("style");
+        Defs = new();
     }
 
     public StyleContext(SvgDocument document)
     {
         StrokeWidth = FallbackToCssStyle(document.StrokeWidth, document.Style);
         Stroke = FallbackToCssStyle(document.Stroke, document.Style);
-        Fill = FallbackToCssStyle(document.Fill, document.Style, new SvgColorUnit("black"));
+        Fill = FallbackToCssStyle(document.Fill, document.Style, SvgPaintServerUnit.FromColor(Colors.Black));
         FillOpacity = FallbackToCssStyle(document.FillOpacity, document.Style);
         Transform = FallbackToCssStyle(document.Transform, document.Style, new SvgTransformUnit(Matrix3X3.Identity));
         StrokeLineCap = FallbackToCssStyle(document.StrokeLineCap, document.Style);
@@ -47,6 +51,7 @@ public struct StyleContext
             document.ViewBox.Unit.HasValue ? -document.ViewBox.Unit.Value.Value.X : 0,
             document.ViewBox.Unit.HasValue ? -document.ViewBox.Unit.Value.Value.Y : 0);
         InlineStyle = document.Style;
+        Defs = document.Defs;
     }
 
     public StyleContext WithElement(SvgElement element)
@@ -76,7 +81,7 @@ public struct StyleContext
         if (element is IFillable fillableElement)
         {
             styleContext.Fill.Unit = FallbackToCssStyle(fillableElement.Fill, styleContext.Fill,
-                styleContext.InlineStyle, new SvgColorUnit("black")).Unit;
+                styleContext.InlineStyle, SvgPaintServerUnit.FromColor(Colors.Black)).Unit;
             styleContext.FillOpacity.Unit =
                 FallbackToCssStyle(fillableElement.FillOpacity, styleContext.FillOpacity, styleContext.InlineStyle)
                     .Unit;
@@ -160,6 +165,8 @@ public struct StyleContext
             styleContext.InlineStyle.Unit = InlineStyle.Unit;
         }
 
+        styleContext.Defs = Defs;
+
         return styleContext;
     }
 
@@ -174,7 +181,7 @@ public struct StyleContext
         }
 
         SvgStyleUnit? style = inlineStyle.Unit;
-        return style?.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(property.SvgName)
+        return style?.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(property.SvgName, Defs)
                ?? (fallback.HasValue
                    ? new SvgProperty<TUnit>(property.SvgName) { Unit = fallback.Value }
                    : new SvgProperty<TUnit>(property.SvgName));
@@ -191,7 +198,7 @@ public struct StyleContext
         }
 
         SvgStyleUnit? style = inlineStyle.Unit;
-        var styleProp = style?.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(property.SvgName);
+        var styleProp = style?.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(property.SvgName, Defs);
         if (styleProp != null) return styleProp;
         if(parentStyleProperty.Unit != null)
         {

+ 31 - 19
src/PixiEditor.SVG/SvgDocument.cs

@@ -1,20 +1,21 @@
 using System.Xml;
 using System.Xml.Linq;
 using Drawie.Numerics;
+using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Enums;
 using PixiEditor.SVG.Features;
 using PixiEditor.SVG.Units;
 
 namespace PixiEditor.SVG;
 
-public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFillable, IStrokable, IOpacity
+public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFillable, IStrokable, IOpacity, IDefsStorage
 {
     public string RootNamespace { get; set; } = "http://www.w3.org/2000/svg";
     public string Version { get; set; } = "1.1";
 
     public SvgProperty<SvgRectUnit> ViewBox { get; } = new("viewBox");
-    public SvgProperty<SvgColorUnit> Fill { get; } = new("fill");
-    public SvgProperty<SvgColorUnit> Stroke { get; } = new("stroke");
+    public SvgProperty<SvgPaintServerUnit> Fill { get; } = new("fill");
+    public SvgProperty<SvgPaintServerUnit> Stroke { get; } = new("stroke");
     public SvgProperty<SvgNumericUnit> StrokeWidth { get; } = new("stroke-width");
 
     public SvgProperty<SvgEnumUnit<SvgStrokeLineCap>> StrokeLineCap { get; } = new("stroke-linecap");
@@ -23,6 +24,8 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
     public SvgProperty<SvgTransformUnit> Transform { get; } = new("transform");
     public SvgProperty<SvgNumericUnit> Opacity { get; } = new("opacity");
     public SvgProperty<SvgNumericUnit> FillOpacity { get; } = new("fill-opacity");
+
+    public SvgDefs Defs { get; set; } = new();
     public List<SvgElement> Children { get; } = new();
 
     public SvgDocument() : base("svg")
@@ -34,7 +37,7 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
         ViewBox.Unit = new SvgRectUnit(viewBox);
     }
 
-    public override void ParseData(XmlReader reader)
+    public override void ParseData(XmlReader reader, SvgDefs defs)
     {
         List<SvgProperty> properties = new()
         {
@@ -46,10 +49,10 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
             ViewBox,
             StrokeLineCap,
             StrokeLineJoin,
-            Opacity
+            Opacity,
         };
 
-        ParseAttributes(properties, reader);
+        ParseAttributes(properties, reader, defs); // TODO: merge with Defs?
     }
 
     public string ToXml()
@@ -66,16 +69,25 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
 
         GatherRequiredNamespaces(usedNamespaces, Children);
 
-        foreach (var usedNamespace in usedNamespaces)
+        DefStorage defs = new(this);
+
+        AppendProperties(document.Root, defs);
+
+
+        foreach (SvgElement child in Children)
         {
-            document.Root.Add(new XAttribute(XNamespace.Xmlns + usedNamespace.Key, usedNamespace.Value));
+            document.Root.Add(child.ToXml(ns, defs));
         }
 
-        AppendProperties(document.Root);
+        if (Defs?.Children.Count > 0)
+        {
+            document.Root.Add(Defs.ToXml(ns, defs));
+            GatherRequiredNamespaces(usedNamespaces, Defs.Children);
+        }
 
-        foreach (SvgElement child in Children)
+        foreach (var usedNamespace in usedNamespaces)
         {
-            document.Root.Add(child.ToXml(ns));
+            document.Root.Add(new XAttribute(XNamespace.Xmlns + usedNamespace.Key, usedNamespace.Value));
         }
 
         return document.ToString();
@@ -103,41 +115,41 @@ public class SvgDocument : SvgElement, IElementContainer, ITransformable, IFilla
         }
     }
 
-    private void AppendProperties(XElement? root)
+    private void AppendProperties(XElement? root, DefStorage defs)
     {
         if (ViewBox.Unit != null)
         {
-            root.Add(new XAttribute("viewBox", ViewBox.Unit.Value.ToXml()));
+            root.Add(new XAttribute("viewBox", ViewBox.Unit.Value.ToXml(defs)));
         }
 
         if (Fill.Unit != null)
         {
-            root.Add(new XAttribute("fill", Fill.Unit.Value.ToXml()));
+            root.Add(new XAttribute("fill", Fill.Unit.Value.ToXml(defs)));
         }
 
         if (Stroke.Unit != null)
         {
-            root.Add(new XAttribute("stroke", Stroke.Unit.Value.ToXml()));
+            root.Add(new XAttribute("stroke", Stroke.Unit.Value.ToXml(defs)));
         }
 
         if (StrokeWidth.Unit != null)
         {
-            root.Add(new XAttribute("stroke-width", StrokeWidth.Unit.Value.ToXml()));
+            root.Add(new XAttribute("stroke-width", StrokeWidth.Unit.Value.ToXml(defs)));
         }
 
         if (Transform.Unit != null)
         {
-            root.Add(new XAttribute("transform", Transform.Unit.Value.ToXml()));
+            root.Add(new XAttribute("transform", Transform.Unit.Value.ToXml(defs)));
         }
 
         if (StrokeLineCap.Unit != null)
         {
-            root.Add(new XAttribute("stroke-linecap", StrokeLineCap.Unit.Value.ToXml()));
+            root.Add(new XAttribute("stroke-linecap", StrokeLineCap.Unit.Value.ToXml(defs)));
         }
 
         if (StrokeLineJoin.Unit != null)
         {
-            root.Add(new XAttribute("stroke-linejoin", StrokeLineJoin.Unit.Value.ToXml()));
+            root.Add(new XAttribute("stroke-linejoin", StrokeLineJoin.Unit.Value.ToXml(defs)));
         }
     }
 }

+ 38 - 13
src/PixiEditor.SVG/SvgElement.cs

@@ -1,6 +1,7 @@
 using System.Text;
 using System.Xml;
 using System.Xml.Linq;
+using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Exceptions;
 using PixiEditor.SVG.Features;
 using PixiEditor.SVG.Units;
@@ -15,7 +16,7 @@ public class SvgElement(string tagName)
 
     public SvgProperty<SvgStyleUnit> Style { get; } = new("style");
 
-    public XElement ToXml(XNamespace nameSpace)
+    public XElement ToXml(XNamespace nameSpace, DefStorage defs)
     {
         XElement element = new XElement(nameSpace + TagName);
 
@@ -28,18 +29,18 @@ public class SvgElement(string tagName)
                 {
                     if (string.IsNullOrEmpty(prop.SvgName))
                     {
-                        element.Value = prop.Unit.ToXml();
+                        element.Value = prop.Unit.ToXml(defs);
                     }
                     else
                     {
                         if (!string.IsNullOrEmpty(prop.NamespaceName))
                         {
                             XName name = XNamespace.Get(RequiredNamespaces[prop.NamespaceName]) + prop.SvgName;
-                            element.Add(new XAttribute(name, prop.Unit.ToXml()));
+                            element.Add(new XAttribute(name, prop.Unit.ToXml(defs)));
                         }
                         else
                         {
-                            element.Add(new XAttribute(prop.SvgName, prop.Unit.ToXml()));
+                            element.Add(new XAttribute(prop.SvgName, prop.Unit.ToXml(defs)));
                         }
                     }
                 }
@@ -50,20 +51,44 @@ public class SvgElement(string tagName)
         {
             foreach (SvgElement child in container.Children)
             {
-                element.Add(child.ToXml(nameSpace));
+                element.Add(child.ToXml(nameSpace, defs));
             }
         }
 
         return element;
     }
 
-    public virtual void ParseData(XmlReader reader)
+    public virtual void ParseData(XmlReader reader, SvgDefs defs)
     {
         // This is supposed to be overriden by child classes
         throw new SvgParsingException($"Element {TagName} does not support parsing");
     }
 
-    protected void ParseAttributes(List<SvgProperty> properties, XmlReader reader)
+    /// <summary>
+    /// Gets unit for property. If property does not have unit, it will try to get it from inlined style.
+    /// </summary>
+    /// <param name="forProperty">Property to get unit for</param>
+    /// <param name="defs">Optional defs element to get units from</param>
+    /// <typeparam name="TUnit">Type of unit to get</typeparam>
+    /// <returns>Unit for property</returns>
+    public TUnit? GetUnit<TUnit>(SvgProperty<TUnit> forProperty, SvgDefs defs = default)
+        where TUnit : struct, ISvgUnit
+    {
+        if (forProperty.Unit != null) return forProperty.Unit.Value;
+
+        if (Style.Unit != null)
+        {
+            var styleProp = Style.Unit.Value.TryGetStyleFor<SvgProperty<TUnit>, TUnit>(forProperty.SvgName, defs);
+            if (styleProp != null && styleProp.Unit != null)
+            {
+                return styleProp.Unit.Value;
+            }
+        }
+
+        return null;
+    }
+
+    protected void ParseAttributes(List<SvgProperty> properties, XmlReader reader, SvgDefs defs)
     {
         if (!properties.Contains(Id))
         {
@@ -81,27 +106,27 @@ public class SvgElement(string tagName)
                 string.Equals(x.SvgName, reader.Name, StringComparison.OrdinalIgnoreCase));
             if (matchingProperty != null)
             {
-                ParseAttribute(matchingProperty, reader);
+                ParseAttribute(matchingProperty, reader, defs);
             }
         } while (reader.MoveToNextAttribute());
     }
 
-    private void ParseAttribute(SvgProperty property, XmlReader reader)
+    private void ParseAttribute(SvgProperty property, XmlReader reader, SvgDefs defs)
     {
         if (property is SvgList list)
         {
-            ParseListProperty(list, reader);
+            ParseListProperty(list, reader, defs);
         }
         else
         {
             property.Unit ??= property.CreateDefaultUnit();
-            property.Unit.ValuesFromXml(reader.Value);
+            property.Unit.ValuesFromXml(reader.Value, defs);
         }
     }
 
-    private void ParseListProperty(SvgList list, XmlReader reader)
+    private void ParseListProperty(SvgList list, XmlReader reader, SvgDefs defs)
     {
         list.Unit ??= list.CreateDefaultUnit();
-        list.Unit.ValuesFromXml(reader.Value);
+        list.Unit.ValuesFromXml(reader.Value, defs);
     }
 }

+ 42 - 13
src/PixiEditor.SVG/SvgParser.cs

@@ -21,7 +21,12 @@ public class SvgParser
         { "mask", typeof(SvgMask) },
         { "image", typeof(SvgImage) },
         { "svg", typeof(SvgDocument) },
-        { "text", typeof(SvgText) }
+        { "text", typeof(SvgText) },
+        { "linearGradient", typeof(SvgLinearGradient) },
+        { "radialGradient", typeof(SvgRadialGradient) },
+        { "stop", typeof(SvgStop) },
+        { "defs", typeof(SvgDefs) },
+        { "clipPath", typeof(SvgClipPath) }
     };
 
     public string Source { get; set; }
@@ -41,25 +46,28 @@ public class SvgParser
         {
             return null;
         }
-        
-        SvgDocument root = (SvgDocument)ParseElement(reader)!;
+
+        SvgDocument root = (SvgDocument)ParseElement(reader, new SvgDefs())!;
 
         RectD bounds = ParseBounds(reader); // this takes into account viewBox, width, height, x, y
-        
+
         root.ViewBox.Unit = new SvgRectUnit(bounds);
 
+        using var defsReader = document.CreateReader();
+        root.Defs = ParseDefs(defsReader);
+
         while (reader.Read())
         {
             if (reader.NodeType == XmlNodeType.Element)
             {
-                SvgElement? element = ParseElement(reader);
+                SvgElement? element = ParseElement(reader, root.Defs);
                 if (element != null)
                 {
                     root.Children.Add(element);
 
-                    if (element is IElementContainer container)
+                    if (element is IElementContainer container && element.TagName != "defs")
                     {
-                        ParseChildren(reader, container, element.TagName);
+                        ParseChildren(reader, container, root.Defs, element.TagName);
                     }
                 }
             }
@@ -68,38 +76,59 @@ public class SvgParser
         return root;
     }
 
-    private void ParseChildren(XmlReader reader, IElementContainer container, string tagName)
+    private SvgDefs ParseDefs(XmlReader reader)
+    {
+        XmlNodeType node = reader.MoveToContent();
+        if (node != XmlNodeType.Element)
+        {
+            return null;
+        }
+
+        while (reader.Read())
+        {
+            if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "defs")
+            {
+                break;
+            }
+        }
+
+        SvgDefs defs = new();
+        ParseChildren(reader, defs, new SvgDefs(), "defs");
+        return defs;
+    }
+
+    private void ParseChildren(XmlReader reader, IElementContainer container, SvgDefs defs, string tagName)
     {
         while (reader.Read())
         {
             if (reader.NodeType == XmlNodeType.Element)
             {
-                SvgElement? element = ParseElement(reader);
+                SvgElement? element = ParseElement(reader, defs);
                 if (element != null)
                 {
                     container.Children.Add(element);
 
                     if (element is IElementContainer childContainer)
                     {
-                        ParseChildren(reader, childContainer, element.TagName);
+                        ParseChildren(reader, childContainer, defs, element.TagName);
                     }
                 }
             }
-            else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == tagName)
+            else if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == tagName)
             {
                 break;
             }
         }
     }
 
-    private SvgElement? ParseElement(XmlReader reader)
+    private SvgElement? ParseElement(XmlReader reader, SvgDefs defs)
     {
         if (wellKnownElements.TryGetValue(reader.LocalName, out Type elementType))
         {
             SvgElement element = (SvgElement)Activator.CreateInstance(elementType);
             if (reader.MoveToFirstAttribute())
             {
-                element.ParseData(reader);
+                element.ParseData(reader, defs);
             }
 
             return element;

+ 3 - 2
src/PixiEditor.SVG/Units/SvgColorUnit.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core.ColorsImpl;
+using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Exceptions;
 using PixiEditor.SVG.Utils;
 
@@ -52,12 +53,12 @@ public struct SvgColorUnit : ISvgUnit
         return new SvgColorUnit($"hsla({h},{s}%,{l}%,{a})");
     }
 
-    public string ToXml()
+    public string ToXml(DefStorage defs)
     {
         return Value;
     }
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
         Value = readerValue;
     }

+ 3 - 2
src/PixiEditor.SVG/Units/SvgEnumUnit.cs

@@ -1,5 +1,6 @@
 using System.Reflection;
 using PixiEditor.SVG.Attributes;
+using PixiEditor.SVG.Elements;
 using PixiEditor.SVG.Helpers;
 
 namespace PixiEditor.SVG.Units;
@@ -13,7 +14,7 @@ public struct SvgEnumUnit<T> : ISvgUnit where T : struct, Enum
         Value = value;
     }
 
-    public string ToXml()
+    public string ToXml(DefStorage defs)
     {
         FieldInfo field = Value.GetType().GetField(Value.ToString());
         SvgValueAttribute attribute = field.GetCustomAttribute<SvgValueAttribute>();
@@ -26,7 +27,7 @@ public struct SvgEnumUnit<T> : ISvgUnit where T : struct, Enum
         return Value.ToString().ToKebabCase();
     }
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
         bool matched = TryMatchEnum(readerValue);
         if (!matched && Enum.TryParse(readerValue.FromKebabToTitleCase(), out T result))

+ 2 - 2
src/PixiEditor.SVG/Units/SvgLinkUnit.cs

@@ -5,12 +5,12 @@ namespace PixiEditor.SVG.Units;
 public struct SvgLinkUnit : ISvgUnit
 {
     public string? ObjectReference { get; set; } 
-    public string ToXml()
+    public string ToXml(DefStorage defs)
     {
         return ObjectReference != null ? $"url(#{ObjectReference}" : string.Empty;
     }
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
         if (readerValue.StartsWith("url(#") && readerValue.EndsWith(')'))
         {

+ 19 - 2
src/PixiEditor.SVG/Units/SvgNumericUnit.cs

@@ -1,4 +1,5 @@
 using System.Globalization;
+using PixiEditor.SVG.Elements;
 
 namespace PixiEditor.SVG.Units;
 
@@ -56,13 +57,13 @@ public struct SvgNumericUnit(double value, string postFix) : ISvgUnit
         return new SvgNumericUnit(value, "%");
     }
 
-    public string ToXml()
+    public string ToXml(DefStorage defs)
     {
         string invariantValue = Value.ToString(CultureInfo.InvariantCulture);
         return $"{invariantValue}{PostFix}";
     }
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
         string? extractedPostFix = ExtractPostFix(readerValue);
 
@@ -111,4 +112,20 @@ public struct SvgNumericUnit(double value, string postFix) : ISvgUnit
 
         return readerValue.Substring(postFixStartIndex);
     }
+
+    public double NormalizedValue(bool clamp = true)
+    {
+        double value = Value;
+        if (PostFix == "%")
+        {
+            value /= 100;
+        }
+
+        if (clamp)
+        {
+            value = Math.Clamp(value, 0, 1);
+        }
+
+        return value;
+    }
 }

+ 145 - 0
src/PixiEditor.SVG/Units/SvgPaintServerUnit.cs

@@ -0,0 +1,145 @@
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using PixiEditor.SVG.Elements;
+using PixiEditor.SVG.Enums;
+using PixiEditor.SVG.Features;
+
+namespace PixiEditor.SVG.Units;
+
+public struct SvgPaintServerUnit : ISvgUnit
+{
+    public Paintable Paintable { get; set; }
+
+    public SvgLinkUnit? LinksTo { get; set; } = null;
+
+    public SvgPaintServerUnit(Paintable paintable)
+    {
+        Paintable = paintable;
+    }
+
+    public static SvgPaintServerUnit FromColor(Color color)
+    {
+        return new SvgPaintServerUnit(new ColorPaintable(color));
+    }
+
+    public string ToXml(DefStorage defs)
+    {
+        if (LinksTo != null)
+        {
+            return LinksTo.Value.ToXml(defs);
+        }
+
+        return TrySerialize(Paintable, defs);
+    }
+
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
+    {
+        var linkUnit = new SvgLinkUnit();
+        linkUnit.ValuesFromXml(readerValue, defs);
+        LinksTo = linkUnit;
+        if (string.IsNullOrEmpty(LinksTo.Value.ObjectReference))
+        {
+            LinksTo = null;
+            SvgColorUnit colorUnit = new SvgColorUnit();
+            colorUnit.ValuesFromXml(readerValue, defs);
+            Paintable = new ColorPaintable(colorUnit.Color);
+        }
+        else
+        {
+            if (defs.TryFindElement(LinksTo.Value.ObjectReference, out SvgElement? element) &&
+                element is IPaintServer server)
+            {
+                Paintable = server.GetPaintable();
+            }
+        }
+    }
+
+    private string TrySerialize(Paintable paintable, DefStorage defs)
+    {
+        if (paintable is ColorPaintable colorPaintable)
+        {
+            return colorPaintable.Color.ToRgbHex();
+        }
+
+        if (paintable is GradientPaintable gradientPaintable)
+        {
+            return TrySerializeGradient(gradientPaintable, defs);
+        }
+
+        return "";
+    }
+
+    private string TrySerializeGradient(GradientPaintable gradientPaintable, DefStorage defs)
+    {
+        switch (gradientPaintable)
+        {
+            case LinearGradientPaintable linearGradientPaintable:
+                return CreateLinearGradient(linearGradientPaintable, defs);
+            case RadialGradientPaintable radialGradientPaintable:
+                return CreateRadialGradient(radialGradientPaintable, defs);
+            default:
+                return "";
+        }
+    }
+
+    private string CreateLinearGradient(LinearGradientPaintable linearGradientPaintable, DefStorage defs)
+    {
+        SvgLinearGradient linearGradient = new SvgLinearGradient();
+        linearGradient.Id.Unit = new SvgStringUnit($"linearGradient{defs.GetNextId()}");
+        linearGradient.X1.Unit = new SvgNumericUnit(linearGradientPaintable.Start.X, "");
+        linearGradient.Y1.Unit = new SvgNumericUnit(linearGradientPaintable.Start.Y, "");
+        linearGradient.X2.Unit = new SvgNumericUnit(linearGradientPaintable.End.X, "");
+        linearGradient.Y2.Unit = new SvgNumericUnit(linearGradientPaintable.End.Y, "");
+        if (linearGradientPaintable.AbsoluteValues)
+        {
+            linearGradient.GradientUnits.Unit = new SvgEnumUnit<SvgRelativityUnit>(SvgRelativityUnit.UserSpaceOnUse);
+        }
+
+        if (linearGradientPaintable.Transform is { IsIdentity: false })
+        {
+            linearGradient.GradientTransform.Unit = new SvgTransformUnit(linearGradientPaintable.Transform.Value);
+        }
+
+        foreach (var stop in linearGradientPaintable.GradientStops)
+        {
+            SvgStop svgStop = new SvgStop();
+            svgStop.Offset.Unit = new SvgNumericUnit(stop.Offset * 100, "%");
+            svgStop.StopColor.Unit = new SvgColorUnit(stop.Color.ToRgbHex());
+            svgStop.StopOpacity.Unit = new SvgNumericUnit(stop.Color.A / 255.0, "");
+            linearGradient.Children.Add(svgStop);
+        }
+
+        defs.AddDef(linearGradient);
+        return $"url(#{linearGradient.Id.Unit.Value.Value})";
+    }
+
+    private string CreateRadialGradient(RadialGradientPaintable radialGradientPaintable, DefStorage defs)
+    {
+        SvgRadialGradient radialGradient = new SvgRadialGradient();
+        radialGradient.Id.Unit = new SvgStringUnit($"radialGradient{defs.GetNextId()}");
+        radialGradient.Cx.Unit = new SvgNumericUnit(radialGradientPaintable.Center.X, "");
+        radialGradient.Cy.Unit = new SvgNumericUnit(radialGradientPaintable.Center.Y, "");
+        radialGradient.R.Unit = new SvgNumericUnit(radialGradientPaintable.Radius, "");
+        if (radialGradientPaintable.AbsoluteValues)
+        {
+            radialGradient.GradientUnits.Unit = new SvgEnumUnit<SvgRelativityUnit>(SvgRelativityUnit.UserSpaceOnUse);
+        }
+
+        if (radialGradientPaintable.Transform is { IsIdentity: false })
+        {
+            radialGradient.GradientTransform.Unit = new SvgTransformUnit(radialGradientPaintable.Transform.Value);
+        }
+
+        foreach (var stop in radialGradientPaintable.GradientStops)
+        {
+            SvgStop svgStop = new SvgStop();
+            svgStop.Offset.Unit = new SvgNumericUnit(stop.Offset * 100, "%");
+            svgStop.StopColor.Unit = new SvgColorUnit(stop.Color.ToRgbHex());
+            svgStop.StopOpacity.Unit = new SvgNumericUnit(stop.Color.A / 255.0, "");
+            radialGradient.Children.Add(svgStop);
+        }
+
+        defs.AddDef(radialGradient);
+        return $"url(#{radialGradient.Id.Unit.Value.Value})";
+    }
+}

+ 3 - 2
src/PixiEditor.SVG/Units/SvgRectUnit.cs

@@ -1,16 +1,17 @@
 using Drawie.Numerics;
+using PixiEditor.SVG.Elements;
 
 namespace PixiEditor.SVG.Units;
 
 public struct SvgRectUnit(RectD rect) : ISvgUnit
 {
     public RectD Value { get; set; } = rect;
-    public string ToXml()
+    public string ToXml(DefStorage defs)
     {
         return $"{Value.X} {Value.Y} {Value.Width} {Value.Height}";
     }
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
         string[] values = readerValue.Split(' ');
         

+ 5 - 3
src/PixiEditor.SVG/Units/SvgStringUnit.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.SVG.Units;
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.SVG.Units;
 
 public struct SvgStringUnit : ISvgUnit
 {
@@ -8,12 +10,12 @@ public struct SvgStringUnit : ISvgUnit
     }
 
     public string Value { get; set; }
-    public string ToXml()
+    public string ToXml(DefStorage defs)
     {
         return Value;
     }
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
         Value = readerValue;
     }

+ 7 - 5
src/PixiEditor.SVG/Units/SvgStyleUnit.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.SVG.Units;
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.SVG.Units;
 
 public struct SvgStyleUnit : ISvgUnit
 {
@@ -35,23 +37,23 @@ public struct SvgStyleUnit : ISvgUnit
         }
     }
 
-    public string ToXml()
+    public string ToXml(DefStorage defs)
     {
         return Value;
     }
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
         Value = readerValue;
     }
 
-    public TProp TryGetStyleFor<TProp, TUnit>(string property) where TProp : SvgProperty<TUnit> where TUnit : struct, ISvgUnit
+    public TProp TryGetStyleFor<TProp, TUnit>(string property, SvgDefs defs) where TProp : SvgProperty<TUnit> where TUnit : struct, ISvgUnit
     {
         if (inlineDefinedProperties.TryGetValue(property, out var definedProperty))
         {
             TProp prop = (TProp)Activator.CreateInstance(typeof(TProp), property);
             var unit = (TUnit)prop.CreateDefaultUnit();
-            unit.ValuesFromXml(definedProperty);
+            unit.ValuesFromXml(definedProperty, defs);
             prop.Unit = unit;
 
             return prop;

+ 4 - 3
src/PixiEditor.SVG/Units/SvgTransformUnit.cs

@@ -1,6 +1,7 @@
 using System.Globalization;
 using System.Numerics;
 using Drawie.Backend.Core.Numerics;
+using PixiEditor.SVG.Elements;
 
 namespace PixiEditor.SVG.Units;
 
@@ -17,7 +18,7 @@ public struct SvgTransformUnit : ISvgUnit
         MatrixValue = matrixValue;
     }
 
-    public string ToXml()
+    public string ToXml(DefStorage defs)
     {
         string translateX = MatrixValue.TransX.ToString(CultureInfo.InvariantCulture);
         string translateY = MatrixValue.TransY.ToString(CultureInfo.InvariantCulture);
@@ -29,11 +30,11 @@ public struct SvgTransformUnit : ISvgUnit
         return $"matrix({scaleX}, {skewY}, {skewX}, {scaleY}, {translateX}, {translateY})";
     }
 
-    public void ValuesFromXml(string readerValue)
+    public void ValuesFromXml(string readerValue, SvgDefs defs)
     {
         if (readerValue.StartsWith("matrix(") && readerValue.EndsWith(")"))
         {
-            string[] values = readerValue[7..^1].Split(", ");
+            string[] values = readerValue[7..^1].Replace(" ", "").Split(",");
             if (values.Length == 6)
             {
                 if (float.TryParse(values[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float scaleX) &&

+ 5 - 3
src/PixiEditor.SVG/Units/SvgUnit.cs

@@ -1,7 +1,9 @@
-namespace PixiEditor.SVG.Units;
+using PixiEditor.SVG.Elements;
+
+namespace PixiEditor.SVG.Units;
 
 public interface ISvgUnit
 {
-    public string ToXml();
-    public void ValuesFromXml(string readerValue);
+    public string ToXml(DefStorage defs);
+    public void ValuesFromXml(string readerValue, SvgDefs defs);
 }

+ 3 - 0
src/PixiEditor.UI.Common/Accents/Base.axaml

@@ -52,6 +52,7 @@
             <Color x:Key="FloatSocketColor">#ffc66d</Color>
             <Color x:Key="DoubleSocketColor">#efb66d</Color>
             <Color x:Key="ColorSocketColor">#8cf2dd</Color>
+            <Color x:Key="PaintableSocketColor">#48b099</Color>
             <Color x:Key="VecDSocketColor">#c984ca</Color>
             <Color x:Key="Vec3DSocketColor">#597513</Color>
             <Color x:Key="VecISocketColor">#c9b4ca</Color>
@@ -145,6 +146,7 @@
             <SolidColorBrush x:Key="DoubleSocketBrush" Color="{StaticResource DoubleSocketColor}"/>
             <SolidColorBrush x:Key="Float1SocketBrush" Color="{StaticResource DoubleSocketColor}"/>
             <SolidColorBrush x:Key="ColorSocketBrush" Color="{StaticResource ColorSocketColor}"/>
+            <SolidColorBrush x:Key="PaintableSocketBrush" Color="{StaticResource PaintableSocketColor}"/>
             <SolidColorBrush x:Key="Half4SocketBrush" Color="{StaticResource ColorSocketColor}"/>
             <SolidColorBrush x:Key="VecDSocketBrush" Color="{StaticResource VecDSocketColor}"/>
             <SolidColorBrush x:Key="Vec3DSocketBrush" Color="{StaticResource Vec3DSocketColor}"/>
@@ -155,6 +157,7 @@
             <SolidColorBrush x:Key="Int1SocketBrush" Color="{StaticResource IntSocketColor}"/>
             <SolidColorBrush x:Key="StringSocketBrush" Color="{StaticResource StringSocketColor}"/>
             <SolidColorBrush x:Key="Matrix3X3SocketBrush" Color="{StaticResource Matrix3X3SocketColor}"/>
+
             <ConicGradientBrush x:Key="ShapeVectorDataSocketBrush" GradientStops="{StaticResource ShapeDataSocketGradient}"/>
             <SolidColorBrush x:Key="EllipseVectorDataSocketBrush" Color="{StaticResource EllipseDataSocketColor}"/>
             <SolidColorBrush x:Key="PointsVectorDataSocketBrush" Color="{StaticResource PointsDataSocketColor}"/>

+ 62 - 0
src/PixiEditor.sln

@@ -132,6 +132,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drawie.Interop.Avalonia.Cor
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.UpdateInstaller.Exe", "PixiEditor.UpdateInstaller.Exe\PixiEditor.UpdateInstaller.Exe.csproj", "{0258658B-78D5-4790-AF27-94B065DF529C}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorPicker.AvaloniaUI", "ColorPicker\src\ColorPicker.AvaloniaUI\ColorPicker.AvaloniaUI.csproj", "{885A99AB-86F0-4D8E-A989-FB0000C1662D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorPicker.Models", "ColorPicker\src\ColorPicker.Models\ColorPicker.Models.csproj", "{ED673353-3433-4FCB-9199-0F8DE0968F52}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|x64 = Debug|x64
@@ -1256,6 +1260,62 @@ Global
 		{0258658B-78D5-4790-AF27-94B065DF529C}.Steam|x64.Build.0 = Debug|Any CPU
 		{0258658B-78D5-4790-AF27-94B065DF529C}.Steam|ARM64.ActiveCfg = Debug|Any CPU
 		{0258658B-78D5-4790-AF27-94B065DF529C}.Steam|ARM64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Debug|x64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.DevRelease|x64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.DevRelease|x64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.DevRelease|ARM64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.DevRelease|ARM64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.DevSteam|x64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.DevSteam|x64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.DevSteam|ARM64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.DevSteam|ARM64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.MSIX Debug|x64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.MSIX Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.MSIX Debug|ARM64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.MSIX|x64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.MSIX|x64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.MSIX|ARM64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.MSIX|ARM64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Release|x64.ActiveCfg = Release|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Release|x64.Build.0 = Release|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Release|ARM64.Build.0 = Release|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Steam|x64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Steam|x64.Build.0 = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Steam|ARM64.ActiveCfg = Debug|Any CPU
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D}.Steam|ARM64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Debug|x64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Debug|ARM64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.DevRelease|x64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.DevRelease|x64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.DevRelease|ARM64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.DevRelease|ARM64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.DevSteam|x64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.DevSteam|x64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.DevSteam|ARM64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.DevSteam|ARM64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.MSIX Debug|x64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.MSIX Debug|ARM64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.MSIX Debug|ARM64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.MSIX|x64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.MSIX|x64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.MSIX|ARM64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.MSIX|ARM64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Release|x64.ActiveCfg = Release|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Release|x64.Build.0 = Release|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Release|ARM64.ActiveCfg = Release|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Release|ARM64.Build.0 = Release|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Steam|x64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Steam|x64.Build.0 = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Steam|ARM64.ActiveCfg = Debug|Any CPU
+		{ED673353-3433-4FCB-9199-0F8DE0968F52}.Steam|ARM64.Build.0 = Debug|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -1316,6 +1376,8 @@ Global
 		{FA293BD3-2D99-47BA-8C4F-53F4997CE99C} = {03CFB32D-E797-41B1-B072-A4FEBA5F8813}
 		{9CD22D8F-3F37-44F8-B106-7C1E02016F82} = {03CFB32D-E797-41B1-B072-A4FEBA5F8813}
 		{0258658B-78D5-4790-AF27-94B065DF529C} = {68C3DA2D-D2EA-426E-A866-0019E425C816}
+		{885A99AB-86F0-4D8E-A989-FB0000C1662D} = {E8A74431-F76F-43B1-BC66-CA05E249E6F4}
+		{ED673353-3433-4FCB-9199-0F8DE0968F52} = {E8A74431-F76F-43B1-BC66-CA05E249E6F4}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {D04B4AB0-CA33-42FD-A909-79966F9255C5}

+ 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.Nodes;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using Drawie.Numerics;

+ 73 - 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 BackendColor = Drawie.Backend.Core.ColorsImpl.Color;
+using GradientStop = Drawie.Backend.Core.ColorsImpl.Paintables.GradientStop;
 
 namespace PixiEditor.Helpers.Extensions;
 
@@ -16,18 +20,82 @@ internal static class ColorHelpers
     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 ToMediaColor(this PaletteColor color) => Color.FromRgb(color.R, color.G, color.B);
-    
+
     public static BackendColor BlendColors(BackendColor bottomColor, BackendColor topColor)
     {
         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));
             return new BackendColor(r, g, b, a);
         }
 
         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.Color.A), 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.Color.A), stop.Offset))),
+        IConicGradientBrush conicGradientBrush => new SweepGradientPaintable(
+            new VecD(conicGradientBrush.Center.Point.X, conicGradientBrush.Center.Point.Y),
+            conicGradientBrush.Angle,
+            conicGradientBrush.GradientStops.Select(stop =>
+                new GradientStop(new BackendColor(stop.Color.R, stop.Color.G, stop.Color.B, stop.Color.A), stop.Offset))),
+        null => null,
+
+    };
+
+    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)
+        },
+        SweepGradientPaintable conicGradientPaintable => new ConicGradientBrush
+        {
+            Angle = conicGradientPaintable.Angle,
+            Center = new RelativePoint(conicGradientPaintable.Center.X, conicGradientPaintable.Center.Y, RelativeUnit.Absolute),
+            GradientStops = ToAvaloniaGradientStops(conicGradientPaintable.GradientStops)
+        },
+        null => null,
+        _ => 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.Vector;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
@@ -12,8 +13,8 @@ public class VectorPathResource
     public string SvgPath { get; set; }
     public StrokeCap StrokeLineCap { get; set; } = StrokeCap.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 PathFillType FillType { get; set; } = PathFillType.Winding;
     
@@ -26,8 +27,8 @@ public class VectorPathResource
         {
             StrokeLineCap = StrokeLineCap,
             StrokeLineJoin = StrokeLineJoin,
-            FillColor = FillColor.ToColor(),
-            StrokeColor = StrokeColor.ToColor(),
+            FillPaintable = FillColor.ToPaintable(),
+            Stroke = StrokeColor.ToPaintable(),
             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.Enums;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Vector;
 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.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.Toolbars;
 using PixiEditor.Models.Handlers.Tools;
@@ -10,6 +13,8 @@ using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.Views.Overlays.TransformOverlay;
+using Color = Drawie.Backend.Core.ColorsImpl.Color;
+using Colors = Drawie.Backend.Core.ColorsImpl.Colors;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
@@ -19,10 +24,10 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 {
     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 T? toolViewModel;
@@ -34,13 +39,13 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     protected IFillableShapeToolbar toolbar;
     private IColorsHandler? colorsVM;
     private bool ignoreNextColorChange = false;
-    
+
     protected abstract bool UseGlobalUndo { get; }
     protected abstract bool ShowApplyButton { get; }
 
     public override bool CanUndo => !UseGlobalUndo && document.TransformHandler.HasUndo;
     public override bool CanRedo => !UseGlobalUndo && document.TransformHandler.HasRedo;
-    
+
     public override ExecutionState Start()
     {
         if (base.Start() == ExecutionState.Error)
@@ -57,13 +62,13 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             return ExecutionState.Error;
         if (!drawOnMask && member is not ILayerHandler)
             return ExecutionState.Error;
-        
+
         if (ActiveMode == ShapeToolMode.Drawing)
         {
             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;
             }
 
@@ -74,7 +79,7 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
             document.TransformHandler.ShowHandles = false;
             document.TransformHandler.IsSizeBoxEnabled = true;
             document.TransformHandler.CanAlignToPixels = AlignToPixels;
-            
+
             return ExecutionState.Success;
         }
 
@@ -88,12 +93,12 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
                 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.Fill = shapeData.FillColor != Colors.Transparent;
+            toolbar.Fill = shapeData.FillPaintable.AnythingVisible;
             initialCorners = shapeData.TransformationCorners;
-            
+
             ShapeCorners corners = vectorLayerHandler.TransformationCorners;
             document.TransformHandler.ShowTransform(
                 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);
         ShapeData shapeData = new ShapeData(rect.Center, rect.Size, corners.RectRotation, (float)StrokeWidth,
-            StrokeColor,
-            FillColor) { AntiAliasing = toolbar.AntiAliasing };
+            StrokePaintable,
+            FillPaintable) { AntiAliasing = toolbar.AntiAliasing };
         return shapeData;
     }
 
@@ -168,8 +173,16 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         internals!.ActionAccumulator.AddFinishedActions(EndDrawAction());
         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();
     }
@@ -188,8 +201,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
         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)
@@ -255,7 +268,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
 
         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.Corners = new ShapeCorners((RectD)lastRect);
     }
@@ -370,7 +384,8 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
         if (mode == ShapeToolMode.Transform)
         {
             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;
         }
     }
@@ -385,12 +400,13 @@ internal abstract class DrawableShapeToolExecutor<T> : SimpleShapeToolExecutor w
     {
         document!.TransformHandler.HideTransform();
     }
-    
+
     private void AddToUndo(ShapeCorners corners)
     {
         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 Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 
 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.Nodes;
-using Drawie.Backend.Core.ColorsImpl;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Handlers;
@@ -13,6 +14,7 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.ViewModels.Document.TransformOverlays;
+using Color = Drawie.Backend.Core.ColorsImpl.Color;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
@@ -20,7 +22,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
 {
     public override ExecutorType Type => ExecutorType.ToolLinked;
 
-    protected Color StrokeColor => toolbar!.StrokeColor.ToColor();
+    protected Paintable StrokePaintable => toolbar!.StrokeBrush.ToPaintable();
     protected double StrokeWidth => toolViewModel!.ToolSize;
     protected abstract bool UseGlobalUndo { get; }
     protected abstract bool ShowApplyButton { get; }
@@ -64,7 +66,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
         {
             if (toolbar.SyncWithPrimaryColor)
             {
-                toolbar.StrokeColor = colorsVM.PrimaryColor.ToColor();
+                toolbar.StrokeBrush = new SolidColorBrush(colorsVM.PrimaryColor.ToColor());
                 ignoreNextColorChange = colorsVM.ColorsTempSwapped;
             }
 
@@ -207,7 +209,7 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
         }
 
         ignoreNextColorChange = ActiveMode == ShapeToolMode.Drawing;
-        toolbar!.StrokeColor = color.ToColor();
+        toolbar!.StrokeBrush = new SolidColorBrush(color.ToColor());
         var colorChangedAction = SettingsChange();
         internals!.ActionAccumulator.AddActions(colorChangedAction);
     }
@@ -260,7 +262,10 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
         var endDrawAction = EndDraw();
         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()
@@ -299,12 +304,12 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
 
     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)
     {
-        toolbar!.StrokeColor = data.StrokeColor.ToColor();
+        toolbar!.StrokeBrush = data.Stroke.ToBrush();
         toolbar!.ToolSize = data.StrokeWidth;
         
         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;
         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;
@@ -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 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)
@@ -45,8 +45,8 @@ internal class RasterEllipseToolExecutor : DrawableShapeToolExecutor<IRasterElli
         lastRect = (RectD)rect;
         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)

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

@@ -22,7 +22,7 @@ internal class RasterLineToolExecutor : LineExecutor<ILineToolHandler>
         VecD dir = GetSignedDirection(startDrawingPos, pos);
         VecD oppositeDir = new VecD(-dir.X, -dir.Y);
         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)
@@ -30,7 +30,7 @@ internal class RasterLineToolExecutor : LineExecutor<ILineToolHandler>
         VecD dir = GetSignedDirection(start, end);
         VecD oppositeDir = new VecD(-dir.X, -dir.Y);
         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()
@@ -38,7 +38,7 @@ internal class RasterLineToolExecutor : LineExecutor<ILineToolHandler>
         VecD dir = GetSignedDirection(startDrawingPos, curPos);
         VecD oppositeDir = new VecD(-dir.X, -dir.Y);
         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)

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

@@ -26,7 +26,7 @@ internal class RasterRectangleToolExecutor : DrawableShapeToolExecutor<IRasterRe
         lastRect = (RectD)rect;
         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
         };
@@ -43,7 +43,7 @@ internal class RasterRectangleToolExecutor : DrawableShapeToolExecutor<IRasterRe
 
     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
         };

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

@@ -1,4 +1,5 @@
 using ChunkyImageLib.DataHolders;
+using Drawie.Backend.Core.Numerics;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
 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 ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Vector;
-using PixiEditor.Models.DocumentModels.Public;
 using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Actions;
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.DocumentPassthroughActions;
 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)
         {
-            StrokeColor = StrokeColor, FillColor = FillColor, StrokeWidth = (float)StrokeWidth,
+            Stroke = StrokePaintable, FillPaintable = FillPaintable, StrokeWidth = (float)StrokeWidth,
         };
 
         lastRect = rect;
@@ -77,8 +77,8 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
         return new SetShapeGeometry_Action(memberId,
             new EllipseVectorData(firstCenter, firstRadius)
             {
-                StrokeColor = StrokeColor,
-                FillColor = FillColor,
+                Stroke = StrokePaintable,
+                FillPaintable = FillPaintable,
                 StrokeWidth = (float)StrokeWidth,
                 TransformationMatrix = lastMatrix
             });
@@ -108,8 +108,8 @@ internal class VectorEllipseToolExecutor : DrawableShapeToolExecutor<IVectorElli
 
         EllipseVectorData ellipseData = new EllipseVectorData(firstCenter, firstRadius)
         {
-            StrokeColor = StrokeColor,
-            FillColor = FillColor,
+            Stroke = StrokePaintable,
+            FillPaintable = FillPaintable,
             StrokeWidth = (float)StrokeWidth,
             TransformationMatrix = matrix
         };

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

@@ -92,8 +92,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
 
                 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
@@ -166,8 +166,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
     {
         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 })
             {
                 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,
                 StrokeLineCap = vectorPathToolHandler.StrokeLineCap,
                 StrokeLineJoin = vectorPathToolHandler.StrokeLineJoin
@@ -211,8 +211,8 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
         return new PathVectorData(new VectorPath(path) { FillType = (PathFillType)vectorPathToolHandler.FillMode })
         {
             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,
             StrokeLineCap = vectorPathToolHandler.StrokeLineCap,
             StrokeLineJoin = vectorPathToolHandler.StrokeLineJoin
@@ -260,10 +260,10 @@ internal class VectorPathToolExecutor : UpdateableChangeExecutor, IPathExecutorF
     private void ApplySettings(PathVectorData pathData)
     {
         toolbar.ToolSize = pathData.StrokeWidth;
-        toolbar.StrokeColor = pathData.StrokeColor.ToColor();
+        toolbar.StrokeBrush = pathData.Stroke.ToBrush();
         toolbar.ToolSize = pathData.StrokeWidth;
         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<StrokeCap>>(nameof(VectorPathToolViewModel.StrokeLineCap)).Value = pathData.StrokeLineCap;
         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)
         {
-            StrokeColor = StrokeColor, FillColor = FillColor, StrokeWidth = (float)StrokeWidth,
+            Stroke = StrokePaintable, FillPaintable = FillPaintable, StrokeWidth = (float)StrokeWidth,
         };
 
         lastRect = rect;
@@ -80,8 +80,8 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
         return new SetShapeGeometry_Action(memberId,
             new RectangleVectorData(firstCenter, firstSize)
             {
-                StrokeColor = StrokeColor,
-                FillColor = FillColor,
+                Stroke = StrokePaintable,
+                FillPaintable = FillPaintable,
                 StrokeWidth = (float)StrokeWidth,
                 TransformationMatrix = lastMatrix
             });
@@ -116,8 +116,8 @@ internal class VectorRectangleToolExecutor : DrawableShapeToolExecutor<IVectorRe
 
         RectangleVectorData newData = new RectangleVectorData(firstCenter, firstSize)
         {
-            StrokeColor = data.StrokeColor,
-            FillColor = data.FillColor,
+            Stroke = data.Stroke,
+            FillPaintable = data.FillPaintable,
             StrokeWidth = data.StrokeWidth,
             TransformationMatrix = matrix
         };

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

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

+ 4 - 1
src/PixiEditor/Models/Files/PixiFileType.cs

@@ -2,6 +2,8 @@
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Models.IO;
 using Drawie.Numerics;
+using PixiEditor.Helpers;
+using PixiEditor.Models.ExceptionHandling;
 using PixiEditor.ViewModels.Document;
 
 namespace PixiEditor.Models.Files;
@@ -32,8 +34,9 @@ internal class PixiFileType : IoFileType
         {
             return SaveResult.IoError;
         }
-        catch
+        catch (Exception e)
         {
+            CrashHelper.SendExceptionInfo(e);
             return SaveResult.UnknownError;
         }
 

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

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using PixiEditor.Extensions.CommonApi.Palettes;
 
 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
 {
     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
 {
-    public Color StrokeColor { get; set; }
+    public IBrush StrokeBrush { get; set; }
     public bool SyncWithPrimaryColor { get; set; }
     public bool AntiAliasing { get; set; }
 }

+ 6 - 4
src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs

@@ -272,8 +272,8 @@ internal class SvgDocumentBuilder : IDocumentBuilder
             return;
         }
 
-        bool hasFill = styleContext.Fill.Unit is { Color.A: > 0 };
-        bool hasStroke = styleContext.Stroke.Unit is { Color.A: > 0 } || styleContext.StrokeWidth.Unit is { PixelsValue: > 0 };
+        bool hasFill = styleContext.Fill.Unit?.Paintable is { AnythingVisible: true };
+        bool hasStroke = styleContext.Stroke.Unit?.Paintable is { AnythingVisible: true } || styleContext.StrokeWidth.Unit is { PixelsValue: > 0 };
         bool hasTransform = styleContext.Transform.Unit is { MatrixValue.IsIdentity: false };
 
         shapeData.Fill = hasFill;
@@ -281,7 +281,9 @@ internal class SvgDocumentBuilder : IDocumentBuilder
         {
             var target = styleContext.Fill.Unit;
             float opacity = (float)(styleContext.FillOpacity.Unit?.Value ?? 1);
-            shapeData.FillColor = target.Value.Color.WithAlpha((byte)(Math.Clamp(opacity, 0, 1) * 255));
+            opacity = Math.Clamp(opacity, 0, 1);
+            shapeData.FillPaintable = target.Value.Paintable;
+            shapeData.FillPaintable?.ApplyOpacity(opacity);
         }
 
         if (hasStroke)
@@ -289,7 +291,7 @@ internal class SvgDocumentBuilder : IDocumentBuilder
             var targetColor = styleContext.Stroke.Unit;
             var targetWidth = styleContext.StrokeWidth.Unit;
 
-            shapeData.StrokeColor = targetColor?.Color ?? Colors.Black;
+            shapeData.Stroke = targetColor?.Paintable ?? Colors.Black;
             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 Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 
@@ -15,9 +16,8 @@ public class EllipseSerializationFactory : VectorShapeSerializationFactory<Ellip
         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,
         out EllipseVectorData original)
     {
@@ -26,9 +26,9 @@ public class EllipseSerializationFactory : VectorShapeSerializationFactory<Ellip
 
         original = new EllipseVectorData(center, radius)
         {
-            StrokeColor = strokeColor,
+            Stroke = strokePaintable,
             Fill = fill,
-            FillColor = fillColor,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             TransformationMatrix = matrix
         };

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

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 
@@ -15,9 +16,8 @@ internal class LineSerializationFactory : VectorShapeSerializationFactory<LineVe
         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,
         out LineVectorData original)
     {
@@ -26,8 +26,8 @@ internal class LineSerializationFactory : VectorShapeSerializationFactory<LineVe
 
         original = new LineVectorData(start, end)
         {
-            StrokeColor = strokeColor,
-            FillColor = fillColor,
+            Stroke = strokePaintable,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             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);
+    }
+}

+ 79 - 0
src/PixiEditor/Models/Serialization/Factories/Paintables/GradientPaintableSerializationFactory.cs

@@ -0,0 +1,79 @@
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+
+namespace PixiEditor.Models.Serialization.Factories.Paintables;
+
+internal abstract class GradientPaintableSerializationFactory<T> : SerializationFactory<byte[], T>,
+    IPaintableSerializationFactory
+    where T : GradientPaintable
+{
+    public override byte[] Serialize(T original)
+    {
+        ByteBuilder builder = new();
+        Serialize(original, builder);
+
+        return builder.Build();
+    }
+
+    public override bool TryDeserialize(object serialized, out T original,
+        (string serializerName, string serializerVersion) serializerData)
+    {
+        if (serialized is not byte[] bytes)
+        {
+            original = null!;
+            return false;
+        }
+
+        ByteExtractor extractor = new(bytes);
+        original = TryDeserialize(extractor) as T;
+
+        return true;
+    }
+
+    public Paintable TryDeserialize(ByteExtractor extractor) => TryDeserializeGradient(extractor);
+    public void Serialize(Paintable paintable, ByteBuilder builder) => SerializeGradient((T)paintable, builder);
+
+    protected void SerializeGradient(T paintable, ByteBuilder builder)
+    {
+        builder.AddBool(paintable.AbsoluteValues);
+        bool hasTransform = paintable.Transform.HasValue && paintable.Transform.Value != Matrix3X3.Identity;
+        builder.AddBool(hasTransform);
+        if (hasTransform)
+        {
+            builder.AddMatrix3X3(paintable.Transform.Value);
+        }
+
+        builder.AddInt(paintable.GradientStops?.Count ?? 0);
+        foreach (var stop in paintable.GradientStops)
+        {
+            builder.AddColor(stop.Color);
+            builder.AddDouble(stop.Offset);
+        }
+
+        SerializeSpecificGradient(paintable, builder);
+    }
+
+    protected T TryDeserializeGradient(ByteExtractor extractor)
+    {
+        bool absoluteValues = extractor.GetBool();
+        Matrix3X3? transform = null;
+        if (extractor.GetBool())
+        {
+            transform = extractor.GetMatrix3X3();
+        }
+
+        int stopsCount = extractor.GetInt();
+        List<GradientStop> stops = new();
+        for (int i = 0; i < stopsCount; i++)
+        {
+            stops.Add(new GradientStop(extractor.GetColor(), extractor.GetDouble()));
+        }
+
+        T paintable = DeserializeGradient(absoluteValues, transform, stops, extractor);
+        return paintable;
+    }
+
+    protected abstract void SerializeSpecificGradient(T paintable, ByteBuilder builder);
+    protected abstract T DeserializeGradient(bool absoluteValues, Matrix3X3? transform, List<GradientStop> stops,
+        ByteExtractor extractor);
+}

+ 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);
+}

+ 26 - 0
src/PixiEditor/Models/Serialization/Factories/Paintables/LinearGradientSerializationFactory.cs

@@ -0,0 +1,26 @@
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
+
+namespace PixiEditor.Models.Serialization.Factories.Paintables;
+
+internal class LinearGradientSerializationFactory : GradientPaintableSerializationFactory<LinearGradientPaintable>
+{
+    public override string DeserializationId { get; } = "PixiEditor.LinearGradientPaintable";
+
+
+    protected override void SerializeSpecificGradient(LinearGradientPaintable paintable, ByteBuilder builder)
+    {
+        builder.AddVecD(paintable.Start);
+        builder.AddVecD(paintable.End);
+    }
+
+    protected override LinearGradientPaintable DeserializeGradient(bool absoluteValues, Matrix3X3? transform,
+        List<GradientStop> stops, ByteExtractor extractor)
+    {
+        VecD start = extractor.GetVecD();
+        VecD end = extractor.GetVecD();
+
+        return new LinearGradientPaintable(start, end, stops) { AbsoluteValues = absoluteValues, Transform = transform };
+    }
+}

+ 24 - 0
src/PixiEditor/Models/Serialization/Factories/Paintables/RadialGradientSerializationFactory.cs

@@ -0,0 +1,24 @@
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
+
+namespace PixiEditor.Models.Serialization.Factories.Paintables;
+
+internal class RadialGradientSerializationFactory : GradientPaintableSerializationFactory<RadialGradientPaintable>
+{
+    public override string DeserializationId { get; } = "PixiEditor.RadialGradientPaintable";
+    protected override void SerializeSpecificGradient(RadialGradientPaintable paintable, ByteBuilder builder)
+    {
+        builder.AddVecD(paintable.Center);
+        builder.AddDouble(paintable.Radius);
+    }
+
+    protected override RadialGradientPaintable DeserializeGradient(bool absoluteValues, Matrix3X3? transform, List<GradientStop> stops,
+        ByteExtractor extractor)
+    {
+        VecD center = extractor.GetVecD();
+        double radius = extractor.GetDouble();
+
+        return new RadialGradientPaintable(center, radius, stops) { AbsoluteValues = absoluteValues, Transform = transform };
+    }
+}

+ 24 - 0
src/PixiEditor/Models/Serialization/Factories/Paintables/SweepGradientSerializationFactory.cs

@@ -0,0 +1,24 @@
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
+
+namespace PixiEditor.Models.Serialization.Factories.Paintables;
+
+internal class SweepGradientSerializationFactory : GradientPaintableSerializationFactory<SweepGradientPaintable>
+{
+    public override string DeserializationId { get; } = "PixiEditor.SweepGradientPaintable";
+    protected override void SerializeSpecificGradient(SweepGradientPaintable paintable, ByteBuilder builder)
+    {
+        builder.AddVecD(paintable.Center);
+        builder.AddDouble(paintable.Angle);
+    }
+
+    protected override SweepGradientPaintable DeserializeGradient(bool absoluteValues, Matrix3X3? transform, List<GradientStop> stops,
+        ByteExtractor extractor)
+    {
+        VecD center = extractor.GetVecD();
+        double angle = extractor.GetDouble();
+
+        return new SweepGradientPaintable(center, angle, stops) { AbsoluteValues = absoluteValues, Transform = transform };
+    }
+}

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

@@ -1,6 +1,8 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Numerics;
 
 namespace PixiEditor.Models.Serialization.Factories;
@@ -13,17 +15,16 @@ internal class PointsDataSerializationFactory : VectorShapeSerializationFactory<
         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,
         out PointsVectorData original)
     {
         List<VecD> points = extractor.GetVecDList();
         original = new PointsVectorData(points)
         {
-            StrokeColor = strokeColor,
-            FillColor = fillColor,
+            Stroke = strokePaintable,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             TransformationMatrix = matrix,
             Fill = fill

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

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 
@@ -16,9 +17,8 @@ internal class RectangleSerializationFactory : VectorShapeSerializationFactory<R
         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,
         out RectangleVectorData original)
     {
@@ -27,8 +27,8 @@ internal class RectangleSerializationFactory : VectorShapeSerializationFactory<R
 
         original = new RectangleVectorData(center, size)
         {
-            StrokeColor = strokeColor,
-            FillColor = fillColor,
+            Stroke = strokePaintable,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             TransformationMatrix = matrix,
             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.Paintables;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Vector;
 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,
         out TextVectorData original)
     {
@@ -93,9 +95,9 @@ internal class TextSerializationFactory : VectorShapeSerializationFactory<TextVe
         original = new TextVectorData(text)
         {
             TransformationMatrix = matrix,
-            StrokeColor = strokeColor,
+            Stroke = strokePaintable,
             Fill = fill,
-            FillColor = fillColor,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             Position = position,
             Font = font,

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

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Vector;
 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,
         out PathVectorData original)
     {
@@ -48,11 +48,11 @@ internal class VectorPathSerializationFactory : VectorShapeSerializationFactory<
 
         original = new PathVectorData(path)
         {
-            StrokeColor = strokeColor,
-            FillColor = fillColor,
+            Stroke = strokePaintable,
+            FillPaintable = fillPaintable,
             StrokeWidth = strokeWidth,
             TransformationMatrix = matrix,
-            Fill = fill
+            Fill = fill, // TODO: Check if stroke line cap and join are serialized
         };
 
         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.Paintables;
 using Drawie.Backend.Core.Numerics;
+using PixiEditor.Models.Serialization.Factories.Paintables;
 
 namespace PixiEditor.Models.Serialization.Factories;
 
 public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<byte[], T> where T : ShapeVectorData
 {
+    private static List<SerializationFactory>? paintableFactories = null;
+    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.IsAssignableTo(typeof(IPaintableSerializationFactory)) && type is { IsAbstract: false, IsInterface: false })
+            {
+                factories.Add((SerializationFactory)Activator.CreateInstance(type));
+            }
+        }
+
+        return factories;
+    }
+
     public override byte[] Serialize(T original)
     {
         ByteBuilder builder = new ByteBuilder();
         builder.AddMatrix3X3(original.TransformationMatrix);
-        builder.AddColor(original.StrokeColor);
+        AddPaintable(original.Stroke, builder);
         builder.AddBool(original.Fill);
-        builder.AddColor(original.FillColor);
+        AddPaintable(original.FillPaintable, builder);
         builder.AddFloat(original.StrokeWidth);
-        
+
         AddSpecificData(builder, original);
-        
+
         return builder.Build();
     }
-    
+
     protected abstract void AddSpecificData(ByteBuilder builder, T original);
 
     public override bool TryDeserialize(object serialized, out T original,
@@ -30,16 +53,21 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
             original = null;
             return false;
         }
-        
+
+        bool fileIsPrePaintables = serializerData.serializerName == "PixiEditor"
+                                   && Version.TryParse(serializerData.serializerVersion, out Version version)
+                                   && version is { Major: 2, Minor: 0, Build: 0, Revision: < 62 };
+
         ByteExtractor extractor = new ByteExtractor(data);
-        
+
         Matrix3X3 matrix = extractor.GetMatrix3X3();
-        Color strokeColor = extractor.GetColor();
+        Paintable strokeColor = TryGetPaintable(extractor, fileIsPrePaintables);
         bool fill = TryGetBool(extractor, serializerData);
-        Color fillColor = extractor.GetColor();
+        Paintable fillColor = TryGetPaintable(extractor, fileIsPrePaintables);
         float strokeWidth;
         // 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();
         }
@@ -48,12 +76,13 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
             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);
 
     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
         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;
             }
@@ -69,4 +99,39 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
 
         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);
+    }
 }

+ 5 - 1
src/PixiEditor/PixiEditor.csproj

@@ -100,10 +100,10 @@
     <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.11.0"/>
     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0"/>
     <PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
-    <PackageReference Include="PixiEditor.ColorPicker.AvaloniaUI" Version="1.0.6"/>
   </ItemGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\ColorPicker\src\ColorPicker.AvaloniaUI\ColorPicker.AvaloniaUI.csproj" />
     <ProjectReference Include="..\Drawie\src\Drawie.Interop.Avalonia\Drawie.Interop.Avalonia.csproj"/>
     <ProjectReference Include="..\Drawie\src\Drawie.Interop.Avalonia.Core\Drawie.Interop.Avalonia.Core.csproj"/>
     <ProjectReference Include="..\PixiDocks\src\PixiDocks.Avalonia\PixiDocks.Avalonia.csproj"/>
@@ -143,6 +143,10 @@
       <DependentUpon>FontFamilySettingView.axaml</DependentUpon>
       <SubType>Code</SubType>
     </Compile>
+    <Compile Update="Views\Nodes\Properties\ColorPropertyView.axaml.cs">
+      <DependentUpon>ColorPropertyView.axaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
   </ItemGroup>
 
   <ItemGroup>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است