Browse Source

Merge pull request #424 from PixiEditor/drawingApiCore

Drawing API Core
Krzysztof Krysiński 2 years ago
parent
commit
ce6167c328
100 changed files with 2509 additions and 709 deletions
  1. 10 4
      src/ChunkyImageLib/Chunk.cs
  2. 77 74
      src/ChunkyImageLib/ChunkyImage.cs
  3. 5 3
      src/ChunkyImageLib/ChunkyImageEx.cs
  4. 6 3
      src/ChunkyImageLib/ChunkyImageLib.csproj
  5. 17 0
      src/ChunkyImageLib/ColorEx.cs
  6. 5 3
      src/ChunkyImageLib/CommittedChunkStorage.cs
  7. 9 14
      src/ChunkyImageLib/DataHolders/ColorBounds.cs
  8. 3 1
      src/ChunkyImageLib/DataHolders/ShapeCorners.cs
  9. 7 5
      src/ChunkyImageLib/DataHolders/ShapeData.cs
  10. 7 4
      src/ChunkyImageLib/IReadOnlyChunkyImage.cs
  11. 10 10
      src/ChunkyImageLib/Operations/BresenhamLineHelper.cs
  12. 13 10
      src/ChunkyImageLib/Operations/BresenhamLineOperation.cs
  13. 11 11
      src/ChunkyImageLib/Operations/ChunkyImageOperation.cs
  14. 14 13
      src/ChunkyImageLib/Operations/ClearPathOperation.cs
  15. 5 6
      src/ChunkyImageLib/Operations/ClearRegionOperation.cs
  16. 11 8
      src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs
  17. 1 0
      src/ChunkyImageLib/Operations/EllipseHelper.cs
  18. 30 26
      src/ChunkyImageLib/Operations/EllipseOperation.cs
  19. 1 0
      src/ChunkyImageLib/Operations/IDrawOperation.cs
  20. 15 13
      src/ChunkyImageLib/Operations/ImageOperation.cs
  21. 30 33
      src/ChunkyImageLib/Operations/OperationHelper.cs
  22. 15 11
      src/ChunkyImageLib/Operations/PathOperation.cs
  23. 11 8
      src/ChunkyImageLib/Operations/PixelOperation.cs
  24. 15 12
      src/ChunkyImageLib/Operations/PixelsOperation.cs
  25. 10 9
      src/ChunkyImageLib/Operations/RectangleOperation.cs
  26. 10 16
      src/ChunkyImageLib/Operations/ReplaceColorOperation.cs
  27. 1 0
      src/ChunkyImageLib/Operations/ResizeOperation.cs
  28. 0 17
      src/ChunkyImageLib/SKColorEx.cs
  29. 7 7
      src/ChunkyImageLib/Shaders/ReplaceColorShader.cs
  30. 8 8
      src/ChunkyImageLib/Shaders/ShaderUtils.cs
  31. 28 25
      src/ChunkyImageLib/Surface.cs
  32. 26 26
      src/ChunkyImageLibTest/ChunkyImageTests.cs
  33. 1 0
      src/ChunkyImageLibTest/ClearRegionOperationTests.cs
  34. 1 0
      src/ChunkyImageLibTest/ImageOperationTests.cs
  35. 1 0
      src/ChunkyImageLibTest/OperationHelperTests.cs
  36. 1 0
      src/ChunkyImageLibTest/RectITests.cs
  37. 9 9
      src/ChunkyImageLibTest/RectangleOperationTests.cs
  38. 1 0
      src/ChunkyImageLibVis/MainWindow.xaml.cs
  39. 3 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/LayerImageChunks_ChangeInfo.cs
  40. 3 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/MaskChunks_ChangeInfo.cs
  41. 2 2
      src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/Selection_ChangeInfo.cs
  42. 3 1
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/Size_ChangeInfo.cs
  43. 2 3
      src/PixiEditor.ChangeableDocument/Changeables/Document.cs
  44. 2 3
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs
  45. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlySelection.cs
  46. 1 0
      src/PixiEditor.ChangeableDocument/Changeables/Layer.cs
  47. 0 1
      src/PixiEditor.ChangeableDocument/Changeables/ReferenceLayer.cs
  48. 9 7
      src/PixiEditor.ChangeableDocument/Changeables/Selection.cs
  49. 1 0
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ApplyLayerMask_Change.cs
  50. 12 9
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ChangeBrightness_UpdateableChange.cs
  51. 2 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ClearSelectedArea_Change.cs
  52. 1 0
      src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs
  53. 5 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawEllipse_UpdateableChange.cs
  54. 9 7
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawLine_UpdateableChange.cs
  55. 3 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRectangle_UpdateableChange.cs
  56. 3 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawingChangeHelper.cs
  57. 5 3
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkCache.cs
  58. 23 20
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs
  59. 6 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFill_Change.cs
  60. 14 11
      src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs
  61. 4 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/PasteImage_UpdateableChange.cs
  62. 22 19
      src/PixiEditor.ChangeableDocument/Changes/Drawing/PathBasedPen_UpdateableChange.cs
  63. 13 11
      src/PixiEditor.ChangeableDocument/Changes/Drawing/PixelPerfectPen_UpdateableChange.cs
  64. 5 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ReplaceColor_Change.cs
  65. 3 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/ShiftLayer_UpdateableChange.cs
  66. 16 13
      src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelectedArea_UpdateableChange.cs
  67. 1 0
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs
  68. 17 14
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs
  69. 7 7
      src/PixiEditor.ChangeableDocument/Changes/Selection/ClearSelection_Change.cs
  70. 9 8
      src/PixiEditor.ChangeableDocument/Changes/Selection/SelectEllipse_UpdateableChange.cs
  71. 10 9
      src/PixiEditor.ChangeableDocument/Changes/Selection/SelectLasso_UpdateableChange.cs
  72. 9 8
      src/PixiEditor.ChangeableDocument/Changes/Selection/SelectRectangle_UpdateableChange.cs
  73. 6 5
      src/PixiEditor.ChangeableDocument/Changes/Selection/SelectionChangeHelper.cs
  74. 9 9
      src/PixiEditor.ChangeableDocument/Changes/Selection/SetSelection_Change.cs
  75. 5 4
      src/PixiEditor.ChangeableDocument/Changes/Selection/TransformSelectionPath_UpdateableChange.cs
  76. 1 6
      src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateLayer_Change.cs
  77. 3 1
      src/PixiEditor.ChangeableDocument/Enums/ResizeAnchorEx.cs
  78. 5 5
      src/PixiEditor.ChangeableDocument/Enums/SelectionModeEx.cs
  79. 17 15
      src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs
  80. 29 26
      src/PixiEditor.ChangeableDocument/Rendering/RenderingContext.cs
  81. 32 0
      src/PixiEditor.DrawingApi.Core/Bridge/DrawingBackendApi.cs
  82. 21 0
      src/PixiEditor.DrawingApi.Core/Bridge/IDrawingBackend.cs
  83. 10 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IBitmapImplementation.cs
  84. 12 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IColorImplementation.cs
  85. 10 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IColorSpaceImplementation.cs
  86. 12 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IImgDataImplementation.cs
  87. 11 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IMatrix3x3Implementation.cs
  88. 28 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs
  89. 15 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPixmapImplementation.cs
  90. 37 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IVectorPathImplementation.cs
  91. 38 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/ICanvasImplementation.cs
  92. 17 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/IImageImplementation.cs
  93. 18 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/ISurfaceImplementation.cs
  94. 212 0
      src/PixiEditor.DrawingApi.Core/ColorsImpl/Color.cs
  95. 400 0
      src/PixiEditor.DrawingApi.Core/ColorsImpl/ColorF.cs
  96. 436 0
      src/PixiEditor.DrawingApi.Core/ColorsImpl/Colors.cs
  97. 15 0
      src/PixiEditor.DrawingApi.Core/Exceptions/InitializationDuplicateException.cs
  98. 414 0
      src/PixiEditor.DrawingApi.Core/Numerics/Matrix3X3.cs
  99. 9 24
      src/PixiEditor.DrawingApi.Core/Numerics/RectD.cs
  100. 28 46
      src/PixiEditor.DrawingApi.Core/Numerics/RectI.cs

+ 10 - 4
src/ChunkyImageLib/Chunk.cs

@@ -1,11 +1,14 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib;
 
 public class Chunk : IDisposable
 {
     private static volatile int chunkCounter = 0;
+    
     /// <summary>
     /// The number of chunks that haven't yet been returned (includes garbage collected chunks).
     /// Used in tests to make sure that all chunks are disposed.
@@ -13,14 +16,17 @@ public class Chunk : IDisposable
     public static int ChunkCounter => chunkCounter;
 
     private bool returned = false;
+    
     /// <summary>
     /// The surface of the chunk
     /// </summary>
     public Surface Surface { get; }
+    
     /// <summary>
     /// The size of the chunk
     /// </summary>
     public VecI PixelSize { get; }
+    
     /// <summary>
     /// The resolution of the chunk
     /// </summary>
@@ -50,9 +56,9 @@ public class Chunk : IDisposable
     /// </summary>
     /// <param name="pos">The destination for the <paramref name="surface"/></param>
     /// <param name="paint">The paint to use while drawing</param>
-    public void DrawOnSurface(SKSurface surface, VecI pos, SKPaint? paint = null)
+    public void DrawOnSurface(DrawingSurface surface, VecI pos, Paint? paint = null)
     {
-        surface.Canvas.DrawSurface(Surface.SkiaSurface, pos.X, pos.Y, paint);
+        surface.Canvas.DrawSurface(Surface.DrawingSurface, pos.X, pos.Y, paint);
     }
 
     /// <summary>
@@ -62,7 +68,7 @@ public class Chunk : IDisposable
     {
         if (returned)
             return;
-        Surface.SkiaSurface.Canvas.Flush();
+        Surface.DrawingSurface.Canvas.Flush();
         returned = true;
         Interlocked.Decrement(ref chunkCounter);
         ChunkPool.Instance.Push(this);

+ 77 - 74
src/ChunkyImageLib/ChunkyImage.cs

@@ -1,10 +1,13 @@
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
+using System.Runtime.CompilerServices;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using OneOf;
 using OneOf.Types;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 [assembly: InternalsVisibleTo("ChunkyImageLibTest")]
 
@@ -30,7 +33,7 @@ namespace ChunkyImageLib;
 /// You can check the current state via queuedOperations.Count == 0
 /// 
 /// Depending on the chosen blend mode the latest chunks contain different things:
-///     - SKBlendMode.Src: default mode, the latest chunks are the same as committed ones but with some or all queued operations applied. 
+///     - BlendMode.Src: default mode, the latest chunks are the same as committed ones but with some or all queued operations applied. 
 ///         This means that operations can work with the existing pixels.
 ///     - Any other blend mode: the latest chunks contain only the things drawn by the queued operations.
 ///         They need to be drawn over the committed chunks to obtain the final image. In this case, operations won't have access to the existing pixels.
@@ -54,12 +57,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     private int commitCounter = 0;
 
     public const int FullChunkSize = ChunkPool.FullChunkSize;
-    private static SKPaint ClippingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.DstIn };
-    private static SKPaint InverseClippingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.DstOut };
-    private static SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
-    private static SKPaint SmoothReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src, FilterQuality = SKFilterQuality.Medium };
-    private static SKPaint AddingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Plus };
-    private readonly SKPaint blendModePaint = new SKPaint() { BlendMode = SKBlendMode.Src };
+    private static Paint ClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstIn };
+    private static Paint InverseClippingPaint { get; } = new Paint() { BlendMode = BlendMode.DstOut };
+    private static Paint ReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src };
+    private static Paint SmoothReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.Medium };
+    private static Paint AddingPaint { get; } = new Paint() { BlendMode = BlendMode.Plus };
+    private readonly Paint blendModePaint = new Paint() { BlendMode = BlendMode.Src };
 
     public VecI CommittedSize { get; private set; }
     public VecI LatestSize { get; private set; }
@@ -75,9 +78,9 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
     private readonly List<(IOperation operation, HashSet<VecI> affectedChunks)> queuedOperations = new();
     private readonly List<ChunkyImage> activeClips = new();
-    private SKBlendMode blendMode = SKBlendMode.Src;
+    private BlendMode blendMode = BlendMode.Src;
     private bool lockTransparency = false;
-    private SKPath? clippingPath;
+    private VectorPath? clippingPath;
     private int? horizontalSymmetryAxis = null;
     private int? verticalSymmetryAxis = null;
 
@@ -157,7 +160,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public SKColor GetCommittedPixel(VecI posOnImage)
+    public Color GetCommittedPixel(VecI posOnImage)
     {
         lock (lockObject)
         {
@@ -166,14 +169,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             var posInChunk = posOnImage - chunkPos * FullChunkSize;
             return MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) switch
             {
-                null => SKColors.Transparent,
+                null => Colors.Transparent,
                 var chunk => chunk.Surface.GetSRGBPixel(posInChunk)
             };
         }
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public SKColor GetMostUpToDatePixel(VecI posOnImage)
+    public Color GetMostUpToDatePixel(VecI posOnImage)
     {
         lock (lockObject)
         {
@@ -187,18 +190,18 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 Chunk? committedChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
                 return committedChunk switch
                 {
-                    null => SKColors.Transparent,
+                    null => Colors.Transparent,
                     _ => committedChunk.Surface.GetSRGBPixel(posInChunk)
                 };
             }
 
             // something is queued, blend mode is Src so no merging needed
-            if (blendMode == SKBlendMode.Src)
+            if (blendMode == BlendMode.Src)
             {
                 Chunk? latestChunk = GetLatestChunk(chunkPos, ChunkResolution.Full);
                 return latestChunk switch
                 {
-                    null => SKColors.Transparent,
+                    null => Colors.Transparent,
                     _ => latestChunk.Surface.GetSRGBPixel(posInChunk)
                 };
             }
@@ -207,19 +210,19 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             {
                 Chunk? committedChunk = MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full);
                 Chunk? latestChunk = GetLatestChunk(chunkPos, ChunkResolution.Full);
-                SKColor committedColor = committedChunk is null ?
-                    SKColors.Transparent :
+                Color committedColor = committedChunk is null ?
+                    Colors.Transparent :
                     committedChunk.Surface.GetSRGBPixel(posInChunk);
-                SKColor latestColor = latestChunk is null ?
-                    SKColors.Transparent :
+                Color latestColor = latestChunk is null ?
+                    Colors.Transparent :
                     latestChunk.Surface.GetSRGBPixel(posInChunk);
                 // using a whole chunk just to draw 1 pixel is kinda dumb,
                 // but this should be faster than any approach that requires allocations
                 using Chunk tempChunk = Chunk.Create(ChunkResolution.Eighth);
-                using SKPaint committedPaint = new SKPaint() { Color = committedColor, BlendMode = SKBlendMode.Src };
-                using SKPaint latestPaint = new SKPaint() { Color = latestColor, BlendMode = this.blendMode };
-                tempChunk.Surface.SkiaSurface.Canvas.DrawPoint(VecI.Zero, committedPaint);
-                tempChunk.Surface.SkiaSurface.Canvas.DrawPoint(VecI.Zero, latestPaint);
+                using Paint committedPaint = new Paint() { Color = committedColor, BlendMode = BlendMode.Src };
+                using Paint latestPaint = new Paint() { Color = latestColor, BlendMode = this.blendMode };
+                tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, committedPaint);
+                tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, latestPaint);
                 return tempChunk.Surface.GetSRGBPixel(VecI.Zero);
             }
         }
@@ -229,7 +232,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     /// True if the chunk existed and was drawn, otherwise false
     /// </returns>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, SKSurface surface, VecI pos, SKPaint? paint = null)
+    public bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
     {
         lock (lockObject)
         {
@@ -250,7 +253,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             var committedChunk = GetCommittedChunk(chunkPos, resolution);
 
             // draw committed directly
-            if (latestChunk.IsT0 || latestChunk.IsT1 && committedChunk is not null && blendMode != SKBlendMode.Src)
+            if (latestChunk.IsT0 || latestChunk.IsT1 && committedChunk is not null && blendMode != BlendMode.Src)
             {
                 if (committedChunk is null)
                     return false;
@@ -259,7 +262,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             }
 
             // no need to combine with committed, draw directly
-            if (blendMode == SKBlendMode.Src || committedChunk is null)
+            if (blendMode == BlendMode.Src || committedChunk is null)
             {
                 if (latestChunk.IsT2)
                 {
@@ -272,11 +275,11 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
             // combine with committed and then draw
             using var tempChunk = Chunk.Create(resolution);
-            tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(committedChunk.Surface.SkiaSurface, 0, 0, ReplacingPaint);
+            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(committedChunk.Surface.DrawingSurface, 0, 0, ReplacingPaint);
             blendModePaint.BlendMode = blendMode;
-            tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(latestChunk.AsT2.Surface.SkiaSurface, 0, 0, blendModePaint);
+            tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(latestChunk.AsT2.Surface.DrawingSurface, 0, 0, blendModePaint);
             if (lockTransparency)
-                OperationHelper.ClampAlpha(tempChunk.Surface.SkiaSurface, committedChunk.Surface.SkiaSurface);
+                OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
             tempChunk.DrawOnSurface(surface, pos, paint);
 
             return true;
@@ -303,7 +306,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    internal bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, SKSurface surface, VecI pos, SKPaint? paint = null)
+    internal bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
     {
         lock (lockObject)
         {
@@ -374,7 +377,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void SetClippingPath(SKPath clippingPath)
+    public void SetClippingPath(VectorPath clippingPath)
     {
         lock (lockObject)
         {
@@ -389,7 +392,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     /// Porter duff compositing operators (apart from SrcOver) likely won't have the intended effect.
     /// </summary>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void SetBlendMode(SKBlendMode mode)
+    public void SetBlendMode(BlendMode mode)
     {
         lock (lockObject)
         {
@@ -435,7 +438,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueReplaceColor(SKColor oldColor, SKColor newColor)
+    public void EnqueueReplaceColor(Color oldColor, Color newColor)
     {
         lock (lockObject)
         {
@@ -457,7 +460,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawEllipse(RectI location, SKColor strokeColor, SKColor fillColor, int strokeWidth, SKPaint? paint = null)
+    public void EnqueueDrawEllipse(RectI location, Color strokeColor, Color fillColor, int strokeWidth, Paint? paint = null)
     {
         lock (lockObject)
         {
@@ -474,7 +477,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     /// Surface is NOT THREAD SAFE, so if you pass a Surface here with copyImage == false you must not do anything with that surface anywhere (not even read) until CommitChanges/CancelChanges is called.
     /// </summary>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawImage(SKMatrix transformMatrix, Surface image, SKPaint? paint = null, bool copyImage = true)
+    public void EnqueueDrawImage(Matrix3X3 transformMatrix, Surface image, Paint? paint = null, bool copyImage = true)
     {
         lock (lockObject)
         {
@@ -488,7 +491,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     /// Be careful about the copyImage argument, see other overload for details
     /// </summary>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawImage(ShapeCorners corners, Surface image, SKPaint? paint = null, bool copyImage = true)
+    public void EnqueueDrawImage(ShapeCorners corners, Surface image, Paint? paint = null, bool copyImage = true)
     {
         lock (lockObject)
         {
@@ -502,7 +505,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     /// Be careful about the copyImage argument, see other overload for details
     /// </summary>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawImage(VecI pos, Surface image, SKPaint? paint = null, bool copyImage = true)
+    public void EnqueueDrawImage(VecI pos, Surface image, Paint? paint = null, bool copyImage = true)
     {
         lock (lockObject)
         {
@@ -514,7 +517,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
     /// <param name="customBounds">Bounds used for affected chunks, will be computed from path in O(n) if null is passed</param>
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawPath(SKPath path, SKColor color, float strokeWidth, SKStrokeCap strokeCap, SKBlendMode blendMode, RectI? customBounds = null)
+    public void EnqueueDrawPath(VectorPath path, Color color, float strokeWidth, StrokeCap strokeCap, BlendMode blendMode, RectI? customBounds = null)
     {
         lock (lockObject)
         {
@@ -525,7 +528,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawBresenhamLine(VecI from, VecI to, SKColor color, SKBlendMode blendMode)
+    public void EnqueueDrawBresenhamLine(VecI from, VecI to, Color color, BlendMode blendMode)
     {
         lock (lockObject)
         {
@@ -536,18 +539,18 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawSkiaLine(VecI from, VecI to, SKStrokeCap strokeCap, float strokeWidth, SKColor color, SKBlendMode blendMode)
+    public void EnqueueDrawSkiaLine(VecI from, VecI to, StrokeCap strokeCap, float strokeWidth, Color color, BlendMode blendMode)
     {
         lock (lockObject)
         {
             ThrowIfDisposed();
-            SkiaLineOperation operation = new(from, to, strokeCap, strokeWidth, color, blendMode);
+            DrawingSurfaceLineOperation operation = new(from, to, strokeCap, strokeWidth, color, blendMode);
             EnqueueOperation(operation);
         }
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawPixels(IEnumerable<VecI> pixels, SKColor color, SKBlendMode blendMode)
+    public void EnqueueDrawPixels(IEnumerable<VecI> pixels, Color color, BlendMode blendMode)
     {
         lock (lockObject)
         {
@@ -558,7 +561,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawPixel(VecI pos, SKColor color, SKBlendMode blendMode)
+    public void EnqueueDrawPixel(VecI pos, Color color, BlendMode blendMode)
     {
         lock (lockObject)
         {
@@ -591,7 +594,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueClearPath(SKPath path, RectI? pathTightBounds = null)
+    public void EnqueueClearPath(VectorPath path, RectI? pathTightBounds = null)
     {
         lock (lockObject)
         {
@@ -663,7 +666,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
             //clear additional state
             activeClips.Clear();
-            blendMode = SKBlendMode.Src;
+            blendMode = BlendMode.Src;
             lockTransparency = false;
             horizontalSymmetryAxis = null;
             verticalSymmetryAxis = null;
@@ -709,7 +712,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             CommittedSize = LatestSize;
             queuedOperations.Clear();
             activeClips.Clear();
-            blendMode = SKBlendMode.Src;
+            blendMode = BlendMode.Src;
             lockTransparency = false;
             horizontalSymmetryAxis = null;
             verticalSymmetryAxis = null;
@@ -747,7 +750,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 }
 
                 // do a swap
-                if (blendMode == SKBlendMode.Src)
+                if (blendMode == BlendMode.Src)
                 {
                     // delete committed version
                     if (committedChunks[resolution].ContainsKey(pos))
@@ -786,13 +789,13 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                     if (lockTransparency)
                     {
                         using Chunk tempChunk = Chunk.Create(resolution);
-                        tempChunk.Surface.SkiaSurface.Canvas.DrawSurface(maybeCommitted.Surface.SkiaSurface, 0, 0, ReplacingPaint);
-                        maybeCommitted.Surface.SkiaSurface.Canvas.DrawSurface(chunk.Surface.SkiaSurface, 0, 0, blendModePaint);
-                        OperationHelper.ClampAlpha(maybeCommitted.Surface.SkiaSurface, tempChunk.Surface.SkiaSurface);
+                        tempChunk.Surface.DrawingSurface.Canvas.DrawSurface(maybeCommitted.Surface.DrawingSurface, 0, 0, ReplacingPaint);
+                        maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0, blendModePaint);
+                        OperationHelper.ClampAlpha(maybeCommitted.Surface.DrawingSurface, tempChunk.Surface.DrawingSurface);
                     }
                     else
                     {
-                        maybeCommitted.Surface.SkiaSurface.Canvas.DrawSurface(chunk.Surface.SkiaSurface, 0, 0, blendModePaint);
+                        maybeCommitted.Surface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0, blendModePaint);
                     }
 
                     chunk.Dispose();
@@ -912,7 +915,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             if (lockTransparency && !chunkData.IsDeleted && MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is not null)
             {
                 var committed = GetCommittedChunk(chunkPos, resolution);
-                OperationHelper.ClampAlpha(targetChunk!.Surface.SkiaSurface, committed!.Surface.SkiaSurface);
+                OperationHelper.ClampAlpha(targetChunk!.Surface.DrawingSurface, committed!.Surface.DrawingSurface);
             }
 
             chunkData.QueueProgress = queuedOperations.Count;
@@ -936,13 +939,13 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
 
         var intersection = Chunk.Create(resolution);
-        intersection.Surface.SkiaSurface.Canvas.Clear(SKColors.White);
+        intersection.Surface.DrawingSurface.Canvas.Clear(Colors.White);
 
         foreach (var mask in activeClips)
         {
             if (mask.CommittedChunkExists(chunkPos))
             {
-                mask.DrawCommittedChunkOn(chunkPos, resolution, intersection.Surface.SkiaSurface, VecI.Zero, ClippingPaint);
+                mask.DrawCommittedChunkOn(chunkPos, resolution, intersection.Surface.DrawingSurface, VecI.Zero, ClippingPaint);
             }
             else
             {
@@ -974,7 +977,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 return chunkData.IsDeleted;
 
             if (chunkData.IsDeleted)
-                targetChunk.Surface.SkiaSurface.Canvas.Clear();
+                targetChunk.Surface.DrawingSurface.Canvas.Clear();
 
             // just regular drawing
             if (combinedRasterClips.IsT0) //Everything is visible as far as raster clips are concerned
@@ -987,14 +990,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             var clip = combinedRasterClips.AsT2;
 
             using var tempChunk = Chunk.Create(targetChunk.Resolution);
-            targetChunk.DrawOnSurface(tempChunk.Surface.SkiaSurface, VecI.Zero, ReplacingPaint);
+            targetChunk.DrawOnSurface(tempChunk.Surface.DrawingSurface, VecI.Zero, ReplacingPaint);
 
             CallDrawWithClip(chunkOperation, tempChunk, resolution, chunkPos);
 
-            clip.DrawOnSurface(tempChunk.Surface.SkiaSurface, VecI.Zero, ClippingPaint);
-            clip.DrawOnSurface(targetChunk.Surface.SkiaSurface, VecI.Zero, InverseClippingPaint);
+            clip.DrawOnSurface(tempChunk.Surface.DrawingSurface, VecI.Zero, ClippingPaint);
+            clip.DrawOnSurface(targetChunk.Surface.DrawingSurface, VecI.Zero, InverseClippingPaint);
 
-            tempChunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, VecI.Zero, AddingPaint);
+            tempChunk.DrawOnSurface(targetChunk.Surface.DrawingSurface, VecI.Zero, AddingPaint);
             return false;
         }
 
@@ -1010,15 +1013,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     {
         if (clippingPath is not null && !clippingPath.IsEmpty)
         {
-            int count = targetChunk.Surface.SkiaSurface.Canvas.Save();
+            int count = targetChunk.Surface.DrawingSurface.Canvas.Save();
 
-            using SKPath transformedPath = new(clippingPath);
+            using VectorPath transformedPath = new(clippingPath);
             float scale = (float)resolution.Multiplier();
             VecD trans = -chunkPos * FullChunkSize * scale;
-            transformedPath.Transform(SKMatrix.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
-            targetChunk.Surface.SkiaSurface.Canvas.ClipPath(transformedPath);
+            transformedPath.Transform(Matrix3X3.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
+            targetChunk.Surface.DrawingSurface.Canvas.ClipPath(transformedPath);
             operation.DrawOnChunk(targetChunk, chunkPos);
-            targetChunk.Surface.SkiaSurface.Canvas.RestoreToCount(count);
+            targetChunk.Surface.DrawingSurface.Canvas.RestoreToCount(count);
         }
         else
         {
@@ -1101,11 +1104,11 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         if (existingFullResChunk is not null)
         {
             var newChunk = Chunk.Create(resolution);
-            newChunk.Surface.SkiaSurface.Canvas.Save();
-            newChunk.Surface.SkiaSurface.Canvas.Scale((float)resolution.Multiplier());
+            newChunk.Surface.DrawingSurface.Canvas.Save();
+            newChunk.Surface.DrawingSurface.Canvas.Scale((float)resolution.Multiplier());
 
-            newChunk.Surface.SkiaSurface.Canvas.DrawSurface(existingFullResChunk.Surface.SkiaSurface, 0, 0, SmoothReplacingPaint);
-            newChunk.Surface.SkiaSurface.Canvas.Restore();
+            newChunk.Surface.DrawingSurface.Canvas.DrawSurface(existingFullResChunk.Surface.DrawingSurface, 0, 0, SmoothReplacingPaint);
+            newChunk.Surface.DrawingSurface.Canvas.Restore();
             committedChunks[resolution][chunkPos] = newChunk;
             return newChunk;
         }
@@ -1134,10 +1137,10 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         if (maybeCommittedAnyRes is not null)
         {
             Chunk newChunk = Chunk.Create(resolution);
-            if (blendMode == SKBlendMode.Src)
+            if (blendMode == BlendMode.Src)
                 maybeCommittedAnyRes.Surface.CopyTo(newChunk.Surface);
             else
-                newChunk.Surface.SkiaSurface.Canvas.Clear();
+                newChunk.Surface.DrawingSurface.Canvas.Clear();
             latestChunks[resolution][chunkPos] = newChunk;
             return newChunk;
         }
@@ -1157,7 +1160,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
         // no previous chunks exist
         var newLatestChunk = Chunk.Create(resolution);
-        newLatestChunk.Surface.SkiaSurface.Canvas.Clear();
+        newLatestChunk.Surface.DrawingSurface.Canvas.Clear();
         latestChunks[resolution][chunkPos] = newLatestChunk;
         return newLatestChunk;
     }

+ 5 - 3
src/ChunkyImageLib/ChunkyImageEx.cs

@@ -1,15 +1,17 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib;
 public static class IReadOnlyChunkyImageEx
 {
     public static void DrawMostUpToDateRegionOn
-        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, SKSurface surface, VecI pos, SKPaint? paint = null)
+        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
     {
         surface.Canvas.Save();
-        surface.Canvas.ClipRect(SKRect.Create(pos, fullResRegion.Size));
+        surface.Canvas.ClipRect(RectD.Create(pos, fullResRegion.Size));
 
         VecI chunkTopLeft = OperationHelper.GetChunkPos(fullResRegion.TopLeft, ChunkyImage.FullChunkSize);
         VecI chunkBotRigth = OperationHelper.GetChunkPos(fullResRegion.BottomRight, ChunkyImage.FullChunkSize);

+ 6 - 3
src/ChunkyImageLib/ChunkyImageLib.csproj

@@ -9,10 +9,13 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="ComputeSharp.Core" Version="2.0.0-alpha.27" />
-    <PackageReference Include="ComputeSharp.Dynamic" Version="2.0.0-alpha.27" />
+    <PackageReference Include="ComputeSharp.Core" Version="2.0.0-alpha.28" />
+    <PackageReference Include="ComputeSharp.Dynamic" Version="2.0.0-alpha.28" />
     <PackageReference Include="OneOf" Version="3.0.216" />
-    <PackageReference Include="SkiaSharp" Version="2.80.3" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\PixiEditor.DrawingApi.Core\PixiEditor.DrawingApi.Core.csproj" />
   </ItemGroup>
 
 </Project>

+ 17 - 0
src/ChunkyImageLib/ColorEx.cs

@@ -0,0 +1,17 @@
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+
+namespace ChunkyImageLib;
+public static class ColorEx
+{
+    public static unsafe ulong ToULong(this Color color)
+    {
+        ulong result = 0;
+        Half* ptr = (Half*)&result;
+        float normalizedAlpha = color.A / 255.0f;
+        ptr[0] = (Half)(color.R / 255f * normalizedAlpha);
+        ptr[1] = (Half)(color.G / 255f * normalizedAlpha);
+        ptr[2] = (Half)(color.B / 255f * normalizedAlpha);
+        ptr[3] = (Half)(normalizedAlpha);
+        return result;
+    }
+}

+ 5 - 3
src/ChunkyImageLib/CommittedChunkStorage.cs

@@ -1,5 +1,7 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib;
 
@@ -7,14 +9,14 @@ public class CommittedChunkStorage : IDisposable
 {
     private bool disposed = false;
     private List<(VecI, Chunk?)> savedChunks = new();
-    private static SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
+    private static Paint ReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src };
 
     public CommittedChunkStorage(ChunkyImage image, HashSet<VecI> committedChunksToSave)
     {
         foreach (var chunkPos in committedChunksToSave)
         {
             Chunk copy = Chunk.Create();
-            if (!image.DrawCommittedChunkOn(chunkPos, ChunkResolution.Full, copy.Surface.SkiaSurface, VecI.Zero, ReplacingPaint))
+            if (!image.DrawCommittedChunkOn(chunkPos, ChunkResolution.Full, copy.Surface.DrawingSurface, VecI.Zero, ReplacingPaint))
             {
                 copy.Dispose();
                 savedChunks.Add((chunkPos, null));

+ 9 - 14
src/ChunkyImageLib/DataHolders/ColorBounds.cs

@@ -1,11 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
 using ComputeSharp;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
 
 namespace ChunkyImageLib.DataHolders;
 
@@ -20,7 +15,7 @@ public struct ColorBounds
     public float UpperB { get; set; }
     public float UpperA { get; set; }
 
-    public ColorBounds(SKColor color)
+    public ColorBounds(Color color)
     {
         static (float lower, float upper) FindInclusiveBoundaryPremul(byte channel, float alpha)
         {
@@ -36,12 +31,12 @@ public struct ColorBounds
             return (subHalf / 255f, addHalf / 255f);
         }
 
-        float a = color.Alpha / 255f;
+        float a = color.A / 255f;
 
-        (LowerR, UpperR) = FindInclusiveBoundaryPremul(color.Red, a);
-        (LowerG, UpperG) = FindInclusiveBoundaryPremul(color.Green, a);
-        (LowerB, UpperB) = FindInclusiveBoundaryPremul(color.Blue, a);
-        (LowerA, UpperA) = FindInclusiveBoundary(color.Alpha);
+        (LowerR, UpperR) = FindInclusiveBoundaryPremul(color.R, a);
+        (LowerG, UpperG) = FindInclusiveBoundaryPremul(color.G, a);
+        (LowerB, UpperB) = FindInclusiveBoundaryPremul(color.B, a);
+        (LowerA, UpperA) = FindInclusiveBoundary(color.A);
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -63,7 +58,7 @@ public struct ColorBounds
     }
     
     [ShaderMethod]
-    public readonly bool IsWithinBounds(float4 color)
+    public readonly bool IsWithinBounds(Float4 color)
     {
         float r = color.R;
         float g = color.G;

+ 3 - 1
src/ChunkyImageLib/DataHolders/ShapeCorners.cs

@@ -1,4 +1,6 @@
-namespace ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace ChunkyImageLib.DataHolders;
 public struct ShapeCorners
 {
     public ShapeCorners(VecD center, VecD size)

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

@@ -1,10 +1,12 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 
 namespace ChunkyImageLib.DataHolders;
 
 public record struct ShapeData
 {
-    public ShapeData(VecD center, VecD size, double rotation, int strokeWidth, SKColor strokeColor, SKColor fillColor, SKBlendMode blendMode = SKBlendMode.SrcOver)
+    public ShapeData(VecD center, VecD size, double rotation, int strokeWidth, Color strokeColor, Color fillColor, BlendMode blendMode = BlendMode.SrcOver)
     {
         StrokeColor = strokeColor;
         FillColor = fillColor;
@@ -14,9 +16,9 @@ public record struct ShapeData
         StrokeWidth = strokeWidth;
         BlendMode = blendMode;
     }
-    public SKColor StrokeColor { get; }
-    public SKColor FillColor { get; }
-    public SKBlendMode BlendMode { get; }
+    public Color StrokeColor { get; }
+    public Color FillColor { get; }
+    public BlendMode BlendMode { get; }
     public VecD Center { get; }
 
     /// <summary>Can be negative to show flipping </summary>

+ 7 - 4
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -1,14 +1,17 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib;
 
 public interface IReadOnlyChunkyImage
 {
-    bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, SKSurface surface, VecI pos, SKPaint? paint = null);
+    bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
     RectI? FindLatestBounds();
-    SKColor GetCommittedPixel(VecI posOnImage);
-    SKColor GetMostUpToDatePixel(VecI posOnImage);
+    Color GetCommittedPixel(VecI posOnImage);
+    Color GetMostUpToDatePixel(VecI posOnImage);
     bool LatestOrCommittedChunkExists(VecI chunkPos);
     HashSet<VecI> FindAffectedChunks(int fromOperationIndex = 0);
     HashSet<VecI> FindCommittedChunks();

+ 10 - 10
src/ChunkyImageLib/Operations/BresenhamLineHelper.cs

@@ -1,20 +1,20 @@
-using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 
 namespace ChunkyImageLib.Operations;
 public static class BresenhamLineHelper
 {
-    public static SKPoint[] GetBresenhamLine(VecI start, VecI end)
+    public static Point[] GetBresenhamLine(VecI start, VecI end)
     {
         int count = Math.Abs((start - end).LongestAxis) + 1;
         if (count > 100000)
-            return new SKPoint[0];
-        SKPoint[] output = new SKPoint[count];
+            return Array.Empty<Point>();
+        Point[] output = new Point[count];
         CalculateBresenhamLine(start, end, output);
         return output;
     }
 
-    private static void CalculateBresenhamLine(VecI start, VecI end, SKPoint[] output)
+    private static void CalculateBresenhamLine(VecI start, VecI end, Point[] output)
     {
         int index = 0;
 
@@ -25,7 +25,7 @@ public static class BresenhamLineHelper
 
         if (x1 == x2 && y1 == y2)
         {
-            output[index] = start;
+            output[index] = new Point(start);
             return;
         }
 
@@ -54,7 +54,7 @@ public static class BresenhamLineHelper
             dy = y1 - y2;
         }
 
-        output[index] = new SKPoint(x, y);
+        output[index] = new Point(x, y);
         index++;
 
         if (dx > dy)
@@ -77,7 +77,7 @@ public static class BresenhamLineHelper
                     x += xi;
                 }
 
-                output[index] = new SKPoint(x, y);
+                output[index] = new Point(x, y);
                 index++;
             }
         }
@@ -101,7 +101,7 @@ public static class BresenhamLineHelper
                     y += yi;
                 }
 
-                output[index] = new SKPoint(x, y);
+                output[index] = new Point(x, y);
                 index++;
             }
         }

+ 13 - 10
src/ChunkyImageLib/Operations/BresenhamLineOperation.cs

@@ -1,5 +1,8 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib.Operations;
 internal class BresenhamLineOperation : IDrawOperation
@@ -7,31 +10,31 @@ internal class BresenhamLineOperation : IDrawOperation
     public bool IgnoreEmptyChunks => false;
     private readonly VecI from;
     private readonly VecI to;
-    private readonly SKColor color;
-    private readonly SKBlendMode blendMode;
-    private readonly SKPoint[] points;
-    private SKPaint paint;
+    private readonly Color color;
+    private readonly BlendMode blendMode;
+    private readonly Point[] points;
+    private Paint paint;
 
-    public BresenhamLineOperation(VecI from, VecI to, SKColor color, SKBlendMode blendMode)
+    public BresenhamLineOperation(VecI from, VecI to, Color color, BlendMode blendMode)
     {
         this.from = from;
         this.to = to;
         this.color = color;
         this.blendMode = blendMode;
-        paint = new SKPaint() { BlendMode = blendMode };
+        paint = new Paint() { BlendMode = blendMode };
         points = BresenhamLineHelper.GetBresenhamLine(from, to);
     }
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
         // a hacky way to make the lines look slightly better on non full res chunks
-        paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
+        paint.Color = new Color(color.R, color.G, color.B, (byte)(color.A * chunk.Resolution.Multiplier()));
 
-        var surf = chunk.Surface.SkiaSurface;
+        var surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
-        surf.Canvas.DrawPoints(SKPointMode.Points, points, paint);
+        surf.Canvas.DrawPoints(PointMode.Points, points, paint);
         surf.Canvas.Restore();
     }
 

+ 11 - 11
src/ChunkyImageLib/Operations/ChunkyImageOperation.cs

@@ -1,5 +1,5 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace ChunkyImageLib.Operations;
 internal class ChunkyImageOperation : IDrawOperation
@@ -21,15 +21,15 @@ internal class ChunkyImageOperation : IDrawOperation
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
-        chunk.Surface.SkiaSurface.Canvas.Save();
+        chunk.Surface.DrawingSurface.Canvas.Save();
 
         {
             VecI pixelPos = chunkPos * ChunkyImage.FullChunkSize;
             VecI topLeftImageCorner = GetTopLeft();
-            SKRect clippingRect = SKRect.Create(
+            RectD clippingRect = RectD.Create(
                 OperationHelper.ConvertForResolution(topLeftImageCorner - pixelPos, chunk.Resolution),
                 OperationHelper.ConvertForResolution(imageToDraw.CommittedSize, chunk.Resolution));
-            chunk.Surface.SkiaSurface.Canvas.ClipRect(clippingRect);
+            chunk.Surface.DrawingSurface.Canvas.ClipRect(clippingRect);
         }
 
         VecI chunkPixelCenter = chunkPos * ChunkyImage.FullChunkSize;
@@ -40,12 +40,12 @@ internal class ChunkyImageOperation : IDrawOperation
         VecI chunkSize = chunk.PixelSize;
         if (mirrorHorizontal)
         {
-            chunk.Surface.SkiaSurface.Canvas.Scale(-1, 1, chunkSize.X / 2f, chunkSize.Y / 2f);
+            chunk.Surface.DrawingSurface.Canvas.Scale(-1, 1, chunkSize.X / 2f, chunkSize.Y / 2f);
             chunkCenterOnImage.X = -chunkCenterOnImage.X;
         }
         if (mirrorVertical)
         {
-            chunk.Surface.SkiaSurface.Canvas.Scale(1, -1, chunkSize.X / 2f, chunkSize.Y / 2f);
+            chunk.Surface.DrawingSurface.Canvas.Scale(1, -1, chunkSize.X / 2f, chunkSize.Y / 2f);
             chunkCenterOnImage.Y = -chunkCenterOnImage.Y;
         }
 
@@ -61,7 +61,7 @@ internal class ChunkyImageOperation : IDrawOperation
         imageToDraw.DrawCommittedChunkOn(
             topLeft,
             chunk.Resolution,
-            chunk.Surface.SkiaSurface,
+            chunk.Surface.DrawingSurface,
             (VecI)((topLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
 
         VecI gridShift = pos % ChunkyImage.FullChunkSize;
@@ -70,7 +70,7 @@ internal class ChunkyImageOperation : IDrawOperation
             imageToDraw.DrawCommittedChunkOn(
             topRight,
             chunk.Resolution,
-            chunk.Surface.SkiaSurface,
+            chunk.Surface.DrawingSurface,
             (VecI)((topRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
         }
         if (gridShift.Y != 0)
@@ -78,7 +78,7 @@ internal class ChunkyImageOperation : IDrawOperation
             imageToDraw.DrawCommittedChunkOn(
             bottomLeft,
             chunk.Resolution,
-            chunk.Surface.SkiaSurface,
+            chunk.Surface.DrawingSurface,
             (VecI)((bottomLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
         }
         if (gridShift.X != 0 && gridShift.Y != 0)
@@ -86,11 +86,11 @@ internal class ChunkyImageOperation : IDrawOperation
             imageToDraw.DrawCommittedChunkOn(
             bottomRight,
             chunk.Resolution,
-            chunk.Surface.SkiaSurface,
+            chunk.Surface.DrawingSurface,
             (VecI)((bottomRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
         }
 
-        chunk.Surface.SkiaSurface.Canvas.Restore();
+        chunk.Surface.DrawingSurface.Canvas.Restore();
     }
 
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)

+ 14 - 13
src/ChunkyImageLib/Operations/ClearPathOperation.cs

@@ -1,31 +1,32 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace ChunkyImageLib.Operations;
 internal class ClearPathOperation : IDrawOperation
 {
-    private SKPath path;
+    private VectorPath path;
     private RectI pathTightBounds;
 
     public bool IgnoreEmptyChunks => true;
 
-    public ClearPathOperation(SKPath path, RectI? pathTightBounds = null)
+    public ClearPathOperation(VectorPath path, RectI? pathTightBounds = null)
     {
-        this.path = new SKPath(path);
-        this.pathTightBounds = (RectI)(pathTightBounds ?? path.TightBounds);
+        this.path = new VectorPath(path);
+        this.pathTightBounds = (pathTightBounds ?? (RectI)path.TightBounds);
     }
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
-        chunk.Surface.SkiaSurface.Canvas.Save();
+        chunk.Surface.DrawingSurface.Canvas.Save();
 
-        using SKPath transformedPath = new(path);
+        using VectorPath transformedPath = new(path);
         float scale = (float)chunk.Resolution.Multiplier();
         VecD trans = -chunkPos * ChunkyImage.FullChunkSize * scale;
-        transformedPath.Transform(SKMatrix.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
-        chunk.Surface.SkiaSurface.Canvas.ClipPath(transformedPath);
-        chunk.Surface.SkiaSurface.Canvas.Clear();
-        chunk.Surface.SkiaSurface.Canvas.Restore();
+        transformedPath.Transform(Matrix3X3.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
+        chunk.Surface.DrawingSurface.Canvas.ClipPath(transformedPath);
+        chunk.Surface.DrawingSurface.Canvas.Clear();
+        chunk.Surface.DrawingSurface.Canvas.Restore();
     }
 
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)
@@ -39,8 +40,8 @@ internal class ClearPathOperation : IDrawOperation
 
     public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
     {
-        var matrix = SKMatrix.CreateScale(verAxisX is not null ? -1 : 1, horAxisY is not null ? -1 : 1, verAxisX ?? 0, horAxisY ?? 0);
-        using var copy = new SKPath(path);
+        var matrix = Matrix3X3.CreateScale(verAxisX is not null ? -1 : 1, horAxisY is not null ? -1 : 1, verAxisX ?? 0, horAxisY ?? 0);
+        using var copy = new VectorPath(path);
         copy.Transform(matrix);
 
         var newRect = pathTightBounds;

+ 5 - 6
src/ChunkyImageLib/Operations/ClearRegionOperation.cs

@@ -1,5 +1,4 @@
-using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace ChunkyImageLib.Operations;
 
@@ -19,10 +18,10 @@ internal class ClearRegionOperation : IDrawOperation
         VecI convPos = OperationHelper.ConvertForResolution(rect.Pos, chunk.Resolution);
         VecI convSize = OperationHelper.ConvertForResolution(rect.Size, chunk.Resolution);
 
-        chunk.Surface.SkiaSurface.Canvas.Save();
-        chunk.Surface.SkiaSurface.Canvas.ClipRect(SKRect.Create(convPos - chunkPos.Multiply(chunk.PixelSize), convSize));
-        chunk.Surface.SkiaSurface.Canvas.Clear();
-        chunk.Surface.SkiaSurface.Canvas.Restore();
+        chunk.Surface.DrawingSurface.Canvas.Save();
+        chunk.Surface.DrawingSurface.Canvas.ClipRect(RectD.Create(convPos - chunkPos.Multiply(chunk.PixelSize), convSize));
+        chunk.Surface.DrawingSurface.Canvas.Clear();
+        chunk.Surface.DrawingSurface.Canvas.Restore();
     }
 
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)

+ 11 - 8
src/ChunkyImageLib/Operations/SkiaLineOperation.cs → src/ChunkyImageLib/Operations/DrawingSurfaceLineOperation.cs

@@ -1,23 +1,26 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib.Operations;
-internal class SkiaLineOperation : IDrawOperation
+internal class DrawingSurfaceLineOperation : IDrawOperation
 {
     public bool IgnoreEmptyChunks => false;
 
-    private SKPaint paint;
+    private Paint paint;
     private readonly VecI from;
     private readonly VecI to;
 
-    public SkiaLineOperation(VecI from, VecI to, SKStrokeCap strokeCap, float strokeWidth, SKColor color, SKBlendMode blendMode)
+    public DrawingSurfaceLineOperation(VecI from, VecI to, StrokeCap strokeCap, float strokeWidth, Color color, BlendMode blendMode)
     {
         paint = new()
         {
             StrokeCap = strokeCap,
             StrokeWidth = strokeWidth,
             Color = color,
-            Style = SKPaintStyle.Stroke,
+            Style = PaintStyle.Stroke,
             BlendMode = blendMode,
         };
         this.from = from;
@@ -26,8 +29,8 @@ internal class SkiaLineOperation : IDrawOperation
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
-        paint.IsAntialias = chunk.Resolution != ChunkResolution.Full;
-        var surf = chunk.Surface.SkiaSurface;
+        paint.IsAntiAliased = chunk.Resolution != ChunkResolution.Full;
+        var surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
@@ -55,7 +58,7 @@ internal class SkiaLineOperation : IDrawOperation
             newFrom = newFrom.ReflectY((int)horAxisY);
             newTo = newTo.ReflectY((int)horAxisY);
         }
-        return new SkiaLineOperation(newFrom, newTo, paint.StrokeCap, paint.StrokeWidth, paint.Color, paint.BlendMode);
+        return new DrawingSurfaceLineOperation(newFrom, newTo, paint.StrokeCap, paint.StrokeWidth, paint.Color, paint.BlendMode);
     }
 
     public void Dispose()

+ 1 - 0
src/ChunkyImageLib/Operations/EllipseHelper.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace ChunkyImageLib.Operations;
 public class EllipseHelper

+ 30 - 26
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -1,5 +1,9 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace ChunkyImageLib.Operations;
 internal class EllipseOperation : IDrawOperation
@@ -7,24 +11,24 @@ internal class EllipseOperation : IDrawOperation
     public bool IgnoreEmptyChunks => false;
 
     private readonly RectI location;
-    private readonly SKColor strokeColor;
-    private readonly SKColor fillColor;
+    private readonly Color strokeColor;
+    private readonly Color fillColor;
     private readonly int strokeWidth;
-    private readonly SKPaint paint;
+    private readonly Paint paint;
     private bool init = false;
-    private SKPath? outerPath;
-    private SKPath? innerPath;
-    private SKPoint[]? ellipse;
-    private SKPoint[]? ellipseFill;
+    private VectorPath? outerPath;
+    private VectorPath? innerPath;
+    private Point[]? ellipse;
+    private Point[]? ellipseFill;
     private RectI? ellipseFillRect;
 
-    public EllipseOperation(RectI location, SKColor strokeColor, SKColor fillColor, int strokeWidth, SKPaint? paint = null)
+    public EllipseOperation(RectI location, Color strokeColor, Color fillColor, int strokeWidth, Paint? paint = null)
     {
         this.location = location;
         this.strokeColor = strokeColor;
         this.fillColor = fillColor;
         this.strokeWidth = strokeWidth;
-        this.paint = paint?.Clone() ?? new SKPaint();
+        this.paint = paint?.Clone() ?? new Paint();
     }
 
     private void Init()
@@ -33,18 +37,18 @@ internal class EllipseOperation : IDrawOperation
         if (strokeWidth == 1)
         {
             var ellipseList = EllipseHelper.GenerateEllipseFromRect(location);
-            ellipse = ellipseList.Select(a => (SKPoint)a).ToArray();
-            if (fillColor.Alpha > 0 || paint.BlendMode != SKBlendMode.SrcOver)
+            ellipse = ellipseList.Select(a => new Point(a)).ToArray();
+            if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
             {
                 (var fill, ellipseFillRect) = EllipseHelper.SplitEllipseIntoRegions(ellipseList, location);
-                ellipseFill = fill.Select(a => (SKPoint)a).ToArray();
+                ellipseFill = fill.Select(a => new Point(a)).ToArray();
             }
         }
         else
         {
-            outerPath = new SKPath();
+            outerPath = new VectorPath();
             outerPath.ArcTo(location, 0, 359, true);
-            innerPath = new SKPath();
+            innerPath = new VectorPath();
             innerPath.ArcTo(location.Inflate(-strokeWidth), 0, 359, true);
         }
     }
@@ -53,36 +57,36 @@ internal class EllipseOperation : IDrawOperation
     {
         if (!init)
             Init();
-        var surf = chunk.Surface.SkiaSurface;
+        var surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
 
-        paint.IsAntialias = chunk.Resolution != ChunkResolution.Full;
+        paint.IsAntiAliased = chunk.Resolution != ChunkResolution.Full;
 
         if (strokeWidth == 1)
         {
-            if (fillColor.Alpha > 0 || paint.BlendMode != SKBlendMode.SrcOver)
+            if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
             {
                 paint.Color = fillColor;
-                surf.Canvas.DrawPoints(SKPointMode.Lines, ellipseFill, paint);
-                surf.Canvas.DrawRect((SKRect)ellipseFillRect!, paint);
+                surf.Canvas.DrawPoints(PointMode.Lines, ellipseFill!, paint);
+                surf.Canvas.DrawRect(ellipseFillRect!.Value, paint);
             }
             paint.Color = strokeColor;
-            surf.Canvas.DrawPoints(SKPointMode.Points, ellipse, paint);
+            surf.Canvas.DrawPoints(PointMode.Points, ellipse!, paint);
         }
         else
         {
-            if (fillColor.Alpha > 0 || paint.BlendMode != SKBlendMode.SrcOver)
+            if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
             {
                 surf.Canvas.Save();
-                surf.Canvas.ClipPath(innerPath);
+                surf.Canvas.ClipPath(innerPath!);
                 surf.Canvas.DrawColor(fillColor, paint.BlendMode);
                 surf.Canvas.Restore();
             }
             surf.Canvas.Save();
-            surf.Canvas.ClipPath(outerPath);
-            surf.Canvas.ClipPath(innerPath, SKClipOperation.Difference);
+            surf.Canvas.ClipPath(outerPath!);
+            surf.Canvas.ClipPath(innerPath!, ClipOperation.Difference);
             surf.Canvas.DrawColor(strokeColor, paint.BlendMode);
             surf.Canvas.Restore();
         }
@@ -93,7 +97,7 @@ internal class EllipseOperation : IDrawOperation
     {
         var chunks = OperationHelper.FindChunksTouchingEllipse
             (location.Center, location.Width / 2.0, location.Height / 2.0, ChunkyImage.FullChunkSize);
-        if (fillColor.Alpha == 0)
+        if (fillColor.A == 0)
         {
             chunks.ExceptWith(OperationHelper.FindChunksFullyInsideEllipse
                 (location.Center, location.Width / 2.0 - strokeWidth * 2, location.Height / 2.0 - strokeWidth * 2, ChunkyImage.FullChunkSize));

+ 1 - 0
src/ChunkyImageLib/Operations/IDrawOperation.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace ChunkyImageLib.Operations;
 

+ 15 - 13
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -1,19 +1,21 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib.Operations;
 
 internal class ImageOperation : IDrawOperation
 {
-    private SKMatrix transformMatrix;
+    private Matrix3X3 transformMatrix;
     private ShapeCorners corners;
     private Surface toPaint;
     private bool imageWasCopied = false;
-    private readonly SKPaint? customPaint;
+    private readonly Paint? customPaint;
 
     public bool IgnoreEmptyChunks => false;
 
-    public ImageOperation(VecI pos, Surface image, SKPaint? paint = null, bool copyImage = true)
+    public ImageOperation(VecI pos, Surface image, Paint? paint = null, bool copyImage = true)
     {
         if (paint is not null)
             customPaint = paint.Clone();
@@ -25,7 +27,7 @@ internal class ImageOperation : IDrawOperation
             BottomRight = pos + image.Size,
             BottomLeft = new VecD(pos.X, pos.Y + image.Size.Y)
         };
-        transformMatrix = SKMatrix.CreateIdentity();
+        transformMatrix = Matrix3X3.CreateIdentity();
         transformMatrix.TransX = pos.X;
         transformMatrix.TransY = pos.Y;
 
@@ -37,7 +39,7 @@ internal class ImageOperation : IDrawOperation
         imageWasCopied = copyImage;
     }
 
-    public ImageOperation(ShapeCorners corners, Surface image, SKPaint? paint = null, bool copyImage = true)
+    public ImageOperation(ShapeCorners corners, Surface image, Paint? paint = null, bool copyImage = true)
     {
         if (paint is not null)
             customPaint = paint.Clone();
@@ -53,7 +55,7 @@ internal class ImageOperation : IDrawOperation
         imageWasCopied = copyImage;
     }
 
-    public ImageOperation(SKMatrix transformMatrix, Surface image, SKPaint? paint = null, bool copyImage = true)
+    public ImageOperation(Matrix3X3 transformMatrix, Surface image, Paint? paint = null, bool copyImage = true)
     {
         if (paint is not null)
             customPaint = paint.Clone();
@@ -83,13 +85,13 @@ internal class ImageOperation : IDrawOperation
         float scaleMult = (float)chunk.Resolution.Multiplier();
         VecD trans = -chunkPos * ChunkPool.FullChunkSize;
 
-        var scaleTrans = SKMatrix.CreateScaleTranslation(scaleMult, scaleMult, (float)trans.X * scaleMult, (float)trans.Y * scaleMult);
-        var finalMatrix = SKMatrix.Concat(scaleTrans, transformMatrix);
+        var scaleTrans = Matrix3X3.CreateScaleTranslation(scaleMult, scaleMult, (float)trans.X * scaleMult, (float)trans.Y * scaleMult);
+        var finalMatrix = Matrix3X3.Concat(scaleTrans, transformMatrix);
 
-        chunk.Surface.SkiaSurface.Canvas.Save();
-        chunk.Surface.SkiaSurface.Canvas.SetMatrix(finalMatrix);
-        chunk.Surface.SkiaSurface.Canvas.DrawSurface(toPaint.SkiaSurface, 0, 0, customPaint);
-        chunk.Surface.SkiaSurface.Canvas.Restore();
+        chunk.Surface.DrawingSurface.Canvas.Save();
+        chunk.Surface.DrawingSurface.Canvas.SetMatrix(finalMatrix);
+        chunk.Surface.DrawingSurface.Canvas.DrawSurface(toPaint.DrawingSurface, 0, 0, customPaint);
+        chunk.Surface.DrawingSurface.Canvas.Restore();
     }
 
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)

+ 30 - 33
src/ChunkyImageLib/Operations/OperationHelper.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 
 namespace ChunkyImageLib.Operations;
 
@@ -20,37 +21,33 @@ public static class OperationHelper
     /// <summary>
     /// toModify[x,y].Alpha = Math.Min(toModify[x,y].Alpha, toGetAlphaFrom[x,y].Alpha)
     /// </summary>
-    public unsafe static void ClampAlpha(SKSurface toModify, SKSurface toGetAlphaFrom)
+    public unsafe static void ClampAlpha(DrawingSurface toModify, DrawingSurface toGetAlphaFrom)
     {
-        using (var map = toModify.PeekPixels())
+        using Pixmap map = toModify.PeekPixels();
+        using Pixmap refMap = toGetAlphaFrom.PeekPixels();
+        long* pixels = (long*)map.GetPixels();
+        long* refPixels = (long*)refMap.GetPixels();
+        int size = map.Width * map.Height;
+        if (map.Width != refMap.Width || map.Height != refMap.Height)
+            throw new ArgumentException("The surfaces must have the same size");
+
+        for (int i = 0; i < size; i++)
         {
-            using (var refMap = toGetAlphaFrom.PeekPixels())
+            long* offset = pixels + i;
+            long* refOffset = refPixels + i;
+            Half* alpha = (Half*)offset + 3;
+            Half* refAlpha = (Half*)refOffset + 3;
+            if (*refAlpha < *alpha)
             {
-                long* pixels = (long*)map.GetPixels();
-                long* refPixels = (long*)refMap.GetPixels();
-                int size = map.Width * map.Height;
-                if (map.Width != refMap.Width || map.Height != refMap.Height)
-                    throw new ArgumentException("The surfaces must have the same size");
-
-                for (int i = 0; i < size; i++)
-                {
-                    long* offset = pixels + i;
-                    long* refOffset = refPixels + i;
-                    Half* alpha = (Half*)offset + 3;
-                    Half* refAlpha = (Half*)refOffset + 3;
-                    if (*refAlpha < *alpha)
-                    {
-                        float a = (float)(*alpha);
-                        float r = (float)(*((Half*)offset)) / a;
-                        float g = (float)(*((Half*)offset + 1)) / a;
-                        float b = (float)(*((Half*)offset + 2)) / a;
-                        float newA = (float)(*refAlpha);
-                        Half newR = (Half)(r * newA);
-                        Half newG = (Half)(g * newA);
-                        Half newB = (Half)(b * newA);
-                        *offset = (*(ushort*)(&newR)) | ((long)*(ushort*)(&newG)) << 16 | ((long)*(ushort*)(&newB)) << 32 | ((long)*(ushort*)(refAlpha)) << 48;
-                    }
-                }
+                float a = (float)(*alpha);
+                float r = (float)(*((Half*)offset)) / a;
+                float g = (float)(*((Half*)offset + 1)) / a;
+                float b = (float)(*((Half*)offset + 2)) / a;
+                float newA = (float)(*refAlpha);
+                Half newR = (Half)(r * newA);
+                Half newG = (Half)(g * newA);
+                Half newB = (Half)(b * newA);
+                *offset = (*(ushort*)(&newR)) | ((long)*(ushort*)(&newG)) << 16 | ((long)*(ushort*)(&newB)) << 32 | ((long)*(ushort*)(refAlpha)) << 48;
             }
         }
     }
@@ -75,11 +72,11 @@ public static class OperationHelper
         };
     }
 
-    public static SKMatrix CreateMatrixFromPoints(ShapeCorners corners, VecD size)
-        => CreateMatrixFromPoints((SKPoint)corners.TopLeft, (SKPoint)corners.TopRight, (SKPoint)corners.BottomRight, (SKPoint)corners.BottomLeft, (float)size.X, (float)size.Y);
+    public static Matrix3X3 CreateMatrixFromPoints(ShapeCorners corners, VecD size)
+        => CreateMatrixFromPoints((Point)corners.TopLeft, (Point)corners.TopRight, (Point)corners.BottomRight, (Point)corners.BottomLeft, (float)size.X, (float)size.Y);
 
     // see https://stackoverflow.com/questions/48416118/perspective-transform-in-skia/72364829#72364829
-    public static SKMatrix CreateMatrixFromPoints(SKPoint topLeft, SKPoint topRight, SKPoint botRight, SKPoint botLeft, float width, float height)
+    public static Matrix3X3 CreateMatrixFromPoints(Point topLeft, Point topRight, Point botRight, Point botLeft, float width, float height)
     {
         (float x1, float y1) = (topLeft.X, topLeft.Y);
         (float x2, float y2) = (topRight.X, topRight.Y);
@@ -97,7 +94,7 @@ public static class OperationHelper
         float persp1 = (-y1 * x2 + x1 * y2 - x1 * y3 - y2 * x4 + y3 * x4 + x2 * y4 + y1 * x3 - y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
         float persp2 = 1;
 
-        return new SKMatrix(scaleX, skewX, transX, skewY, scaleY, transY, persp0, persp1, persp2);
+        return new Matrix3X3(scaleX, skewX, transX, skewY, scaleY, transY, persp0, persp1, persp2);
     }
 
     public static (ShapeCorners, ShapeCorners) CreateStretchedHexagon(VecD centerPos, double hexagonSide, double stretchX)

+ 15 - 11
src/ChunkyImageLib/Operations/PathOperation.cs

@@ -1,29 +1,33 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace ChunkyImageLib.Operations;
 internal class PathOperation : IDrawOperation
 {
-    private readonly SKPath path;
+    private readonly VectorPath path;
 
-    private readonly SKPaint paint;
+    private readonly Paint paint;
     private readonly RectI bounds;
 
     public bool IgnoreEmptyChunks => false;
 
-    public PathOperation(SKPath path, SKColor color, float strokeWidth, SKStrokeCap cap, SKBlendMode blendMode, RectI? customBounds = null)
+    public PathOperation(VectorPath path, Color color, float strokeWidth, StrokeCap cap, BlendMode blendMode, RectI? customBounds = null)
     {
-        this.path = new SKPath(path);
-        paint = new() { Color = color, Style = SKPaintStyle.Stroke, StrokeWidth = strokeWidth, StrokeCap = cap, BlendMode = blendMode };
+        this.path = new VectorPath(path);
+        paint = new() { Color = color, Style = PaintStyle.Stroke, StrokeWidth = strokeWidth, StrokeCap = cap, BlendMode = blendMode };
 
-        RectI floatBounds = customBounds ?? (RectI)((RectD)path.TightBounds).RoundOutwards();
+        RectI floatBounds = customBounds ?? (RectI)(path.TightBounds).RoundOutwards();
         bounds = floatBounds.Inflate((int)Math.Ceiling(strokeWidth) + 1);
     }
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
-        paint.IsAntialias = chunk.Resolution != ChunkResolution.Full;
-        var surf = chunk.Surface.SkiaSurface;
+        paint.IsAntiAliased = chunk.Resolution != ChunkResolution.Full;
+        var surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
@@ -38,8 +42,8 @@ internal class PathOperation : IDrawOperation
 
     public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)
     {
-        var matrix = SKMatrix.CreateScale(verAxisX is not null ? -1 : 1, horAxisY is not null ? -1 : 1, verAxisX ?? 0, horAxisY ?? 0);
-        using var copy = new SKPath(path);
+        var matrix = Matrix3X3.CreateScale(verAxisX is not null ? -1 : 1, horAxisY is not null ? -1 : 1, verAxisX ?? 0, horAxisY ?? 0);
+        using var copy = new VectorPath(path);
         copy.Transform(matrix);
 
         RectI newBounds = bounds;

+ 11 - 8
src/ChunkyImageLib/Operations/PixelOperation.cs

@@ -1,5 +1,8 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib.Operations;
 
@@ -7,24 +10,24 @@ internal class PixelOperation : IDrawOperation
 {
     public bool IgnoreEmptyChunks => false;
     private readonly VecI pixel;
-    private readonly SKColor color;
-    private readonly SKBlendMode blendMode;
-    private readonly SKPaint paint;
+    private readonly Color color;
+    private readonly BlendMode blendMode;
+    private readonly Paint paint;
 
-    public PixelOperation(VecI pixel, SKColor color, SKBlendMode blendMode)
+    public PixelOperation(VecI pixel, Color color, BlendMode blendMode)
     {
         this.pixel = pixel;
         this.color = color;
         this.blendMode = blendMode;
-        paint = new SKPaint() { BlendMode = blendMode };
+        paint = new Paint() { BlendMode = blendMode };
     }
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
         // a hacky way to make the lines look slightly better on non full res chunks
-        paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
+        paint.Color = new Color(color.R, color.G, color.B, (byte)(color.A * chunk.Resolution.Multiplier()));
 
-        SKSurface surf = chunk.Surface.SkiaSurface;
+        DrawingSurface surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);

+ 15 - 12
src/ChunkyImageLib/Operations/PixelsOperation.cs

@@ -1,40 +1,43 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib.Operations;
 
 internal class PixelsOperation : IDrawOperation
 {
     public bool IgnoreEmptyChunks => false;
-    private readonly SKPoint[] pixels;
-    private readonly SKColor color;
-    private readonly SKBlendMode blendMode;
-    private readonly SKPaint paint;
+    private readonly Point[] pixels;
+    private readonly Color color;
+    private readonly BlendMode blendMode;
+    private readonly Paint paint;
 
-    public PixelsOperation(IEnumerable<VecI> pixels, SKColor color, SKBlendMode blendMode)
+    public PixelsOperation(IEnumerable<VecI> pixels, Color color, BlendMode blendMode)
     {
-        this.pixels = pixels.Select(pixel => (SKPoint)pixel).ToArray();
+        this.pixels = pixels.Select(pixel => new Point(pixel.X, pixel.Y)).ToArray();
         this.color = color;
         this.blendMode = blendMode;
-        paint = new SKPaint() { BlendMode = blendMode };
+        paint = new Paint() { BlendMode = blendMode };
     }
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
         // a hacky way to make the lines look slightly better on non full res chunks
-        paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
+        paint.Color = new Color(color.R, color.G, color.B, (byte)(color.A * chunk.Resolution.Multiplier()));
 
-        SKSurface surf = chunk.Surface.SkiaSurface;
+        DrawingSurface surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
-        surf.Canvas.DrawPoints(SKPointMode.Points, pixels, paint);
+        surf.Canvas.DrawPoints(PointMode.Points, pixels, paint);
         surf.Canvas.Restore();
     }
 
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)
     {
-        return pixels.Select(static pixel => OperationHelper.GetChunkPos((VecI)pixel, ChunkyImage.FullChunkSize)).ToHashSet();
+        return pixels.Select(static pixel => OperationHelper.GetChunkPos(pixel, ChunkyImage.FullChunkSize)).ToHashSet();
     }
 
     public IDrawOperation AsMirrored(int? verAxisX, int? horAxisY)

+ 10 - 9
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 
 namespace ChunkyImageLib.Operations;
 
@@ -16,9 +17,9 @@ internal class RectangleOperation : IDrawOperation
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
-        var skiaSurf = chunk.Surface.SkiaSurface;
+        var skiaSurf = chunk.Surface.DrawingSurface;
 
-        var surf = chunk.Surface.SkiaSurface;
+        var surf = chunk.Surface.DrawingSurface;
 
         var rect = RectD.FromCenterAndSize(Data.Center, Data.Size.Abs());
         var innerRect = rect.Inflate(-Data.StrokeWidth);
@@ -31,18 +32,18 @@ internal class RectangleOperation : IDrawOperation
         skiaSurf.Canvas.RotateRadians((float)Data.Angle, (float)rect.Center.X, (float)rect.Center.Y);
 
         // draw fill
-        if (Data.FillColor.Alpha > 0)
+        if (Data.FillColor.A > 0)
         {
             skiaSurf.Canvas.Save();
-            skiaSurf.Canvas.ClipRect((SKRect)innerRect);
+            skiaSurf.Canvas.ClipRect(innerRect);
             skiaSurf.Canvas.DrawColor(Data.FillColor, Data.BlendMode);
             skiaSurf.Canvas.Restore();
         }
 
         // draw stroke
         skiaSurf.Canvas.Save();
-        skiaSurf.Canvas.ClipRect((SKRect)rect);
-        skiaSurf.Canvas.ClipRect((SKRect)innerRect, SKClipOperation.Difference);
+        skiaSurf.Canvas.ClipRect(rect);
+        skiaSurf.Canvas.ClipRect(innerRect, ClipOperation.Difference);
         skiaSurf.Canvas.DrawColor(Data.StrokeColor, Data.BlendMode);
         skiaSurf.Canvas.Restore();
 
@@ -51,9 +52,9 @@ internal class RectangleOperation : IDrawOperation
 
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)
     {
-        if (Math.Abs(Data.Size.X) < 1 || Math.Abs(Data.Size.Y) < 1 || (Data.StrokeColor.Alpha == 0 && Data.FillColor.Alpha == 0))
+        if (Math.Abs(Data.Size.X) < 1 || Math.Abs(Data.Size.Y) < 1 || (Data.StrokeColor.A == 0 && Data.FillColor.A == 0))
             return new();
-        if (Data.FillColor.Alpha != 0 || Math.Abs(Data.Size.X) == 1 || Math.Abs(Data.Size.Y) == 1)
+        if (Data.FillColor.A != 0 || Math.Abs(Data.Size.X) == 1 || Math.Abs(Data.Size.Y) == 1)
             return OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size.Abs(), Data.Angle, ChunkPool.FullChunkSize);
 
         var chunks = OperationHelper.FindChunksTouchingRectangle(Data.Center, Data.Size.Abs(), Data.Angle, ChunkPool.FullChunkSize);

+ 10 - 16
src/ChunkyImageLib/Operations/ReplaceColorOperation.cs

@@ -1,20 +1,14 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
-using ChunkyImageLib.DataHolders;
+using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Shaders;
 using ComputeSharp;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace ChunkyImageLib.Operations;
 internal class ReplaceColorOperation : IDrawOperation
 {
-    private readonly SKColor oldColor;
-    private readonly SKColor newColor;
+    private readonly Color oldColor;
+    private readonly Color newColor;
 
     private readonly ColorBounds oldColorBounds;
     private readonly HlslColorBounds oldColorBoundsHlsl;
@@ -22,12 +16,12 @@ internal class ReplaceColorOperation : IDrawOperation
 
     public bool IgnoreEmptyChunks => true;
 
-    public ReplaceColorOperation(SKColor oldColor, SKColor newColor)
+    public ReplaceColorOperation(Color oldColor, Color newColor)
     {
         this.oldColor = oldColor;
         this.newColor = newColor;
         oldColorBounds = new ColorBounds(oldColor);
-        oldColorBoundsHlsl = new HlslColorBounds(new Float4(oldColor.Red, oldColor.Green, oldColor.Blue, oldColor.Alpha));
+        oldColorBoundsHlsl = new HlslColorBounds(new Float4(oldColor.R, oldColor.G, oldColor.B, oldColor.A));
         newColorBits = newColor.ToULong();
     }
 
@@ -36,11 +30,11 @@ internal class ReplaceColorOperation : IDrawOperation
         ReplaceColor(oldColorBoundsHlsl, newColor, chunk);
     }
 
-    private static void ReplaceColor(HlslColorBounds oldColorBounds, SKColor newColor, Chunk chunk)
+    private static void ReplaceColor(HlslColorBounds oldColorBounds, Color newColor, Chunk chunk)
     {
-        Span<UInt2> span = chunk.Surface.SkiaSurface.PeekPixels().GetPixelSpan<uint2>();
+        Span<UInt2> span = chunk.Surface.DrawingSurface.PeekPixels().GetPixelSpan<UInt2>();
         using var texture = GraphicsDevice.GetDefault()
-            .AllocateReadWriteTexture2D<uint2>(chunk.PixelSize.X, chunk.PixelSize.Y);
+            .AllocateReadWriteTexture2D<UInt2>(chunk.PixelSize.X, chunk.PixelSize.Y);
 
         texture.CopyFrom(span);
 

+ 1 - 0
src/ChunkyImageLib/Operations/ResizeOperation.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace ChunkyImageLib.Operations;
 

+ 0 - 17
src/ChunkyImageLib/SKColorEx.cs

@@ -1,17 +0,0 @@
-using SkiaSharp;
-
-namespace ChunkyImageLib;
-public static class SKColorEx
-{
-    public static unsafe ulong ToULong(this SKColor color)
-    {
-        ulong result = 0;
-        Half* ptr = (Half*)&result;
-        float normalizedAlpha = color.Alpha / 255.0f;
-        ptr[0] = (Half)(color.Red / 255f * normalizedAlpha);
-        ptr[1] = (Half)(color.Green / 255f * normalizedAlpha);
-        ptr[2] = (Half)(color.Blue / 255f * normalizedAlpha);
-        ptr[3] = (Half)(normalizedAlpha);
-        return result;
-    }
-}

+ 7 - 7
src/ChunkyImageLib/Shaders/ReplaceColorShader.cs

@@ -1,17 +1,17 @@
-using ChunkyImageLib.DataHolders;
-using ComputeSharp;
+using ComputeSharp;
 
 namespace ChunkyImageLib.Shaders;
 
 [AutoConstructor]
-public readonly partial struct ReplaceColorShader : IComputeShader
+internal readonly partial struct ReplaceColorShader : IComputeShader
 {
-    public readonly ReadWriteTexture2D<uint2> texture;
+    public readonly ReadWriteTexture2D<UInt2> texture;
     public readonly HlslColorBounds colorBounds;
     public readonly UInt2 newColor;
+    
     public void Execute()
     {
-        uint2 rgba = texture[ThreadIds.XY];
+        UInt2 rgba = texture[ThreadIds.XY];
         Float4 rgbaFloat = ShaderUtils.UnpackPixel(rgba);
         
         if(IsWithinBounds(rgbaFloat))
@@ -20,7 +20,7 @@ public readonly partial struct ReplaceColorShader : IComputeShader
         }
     }
 
-    private bool IsWithinBounds(float4 color)
+    private bool IsWithinBounds(Float4 color)
     {
         float r = color.R;
         float g = color.G;
@@ -47,7 +47,7 @@ public readonly struct HlslColorBounds
     public readonly float UpperB;
     public readonly float UpperA;
 
-    public HlslColorBounds(float4 color)
+    public HlslColorBounds(Float4 color)
     {
         static (float lower, float upper) FindInclusiveBoundaryPremul(float channel, float alpha)
         {

+ 8 - 8
src/ChunkyImageLib/Shaders/ShaderUtils.cs

@@ -1,13 +1,13 @@
 using ComputeSharp;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
 
 namespace ChunkyImageLib.Shaders;
 
 public static class ShaderUtils
 {
-    public static float4 UnpackPixel(uint2 packedPixel)
+    public static Float4 UnpackPixel(UInt2 packedPixel)
     {
-        return new float4(
+        return new Float4(
             Hlsl.Float16ToFloat32(packedPixel.X),
             Hlsl.Float16ToFloat32(packedPixel.X >> 16),
             Hlsl.Float16ToFloat32(packedPixel.Y),
@@ -15,12 +15,12 @@ public static class ShaderUtils
         );
     }
 
-    public static uint2 PackPixel(SKColor color)
+    public static UInt2 PackPixel(Color color)
     {
-        uint convR = (BitConverter.HalfToUInt16Bits((Half)(color.Red / 255f)));
-        uint convG = (BitConverter.HalfToUInt16Bits((Half)(color.Green / 255f)));
-        uint convB = (BitConverter.HalfToUInt16Bits((Half)(color.Blue / 255f)));
-        uint convA = (BitConverter.HalfToUInt16Bits((Half)(color.Alpha / 255f)));
+        uint convR = (BitConverter.HalfToUInt16Bits((Half)(color.R / 255f)));
+        uint convG = (BitConverter.HalfToUInt16Bits((Half)(color.G / 255f)));
+        uint convB = (BitConverter.HalfToUInt16Bits((Half)(color.B / 255f)));
+        uint convA = (BitConverter.HalfToUInt16Bits((Half)(color.A / 255f)));
 
         return new UInt2(convG << 16 | convR, convB | convA << 16);
     }

+ 28 - 25
src/ChunkyImageLib/Surface.cs

@@ -1,7 +1,10 @@
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
-using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace ChunkyImageLib;
 
@@ -9,12 +12,12 @@ public class Surface : IDisposable
 {
     private bool disposed;
     public IntPtr PixelBuffer { get; }
-    public SKSurface SkiaSurface { get; }
+    public DrawingSurface DrawingSurface { get; }
     public int BytesPerPixel { get; }
     public VecI Size { get; }
 
-    private SKPaint drawingPaint = new SKPaint() { BlendMode = SKBlendMode.Src };
-    private SKPaint nearestNeighborReplacingPaint = new SKPaint() { BlendMode = SKBlendMode.Src, FilterQuality = SKFilterQuality.None };
+    private Paint drawingPaint = new Paint() { BlendMode = BlendMode.Src };
+    private Paint nearestNeighborReplacingPaint = new() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.None };
 
     public Surface(VecI size)
     {
@@ -27,45 +30,45 @@ public class Surface : IDisposable
 
         BytesPerPixel = 8;
         PixelBuffer = CreateBuffer(size.X, size.Y, BytesPerPixel);
-        SkiaSurface = CreateSKSurface();
+        DrawingSurface = CreateDrawingSurface();
     }
 
     public Surface(Surface original) : this(original.Size)
     {
-        SkiaSurface.Canvas.DrawSurface(original.SkiaSurface, 0, 0);
+        DrawingSurface.Canvas.DrawSurface(original.DrawingSurface, 0, 0);
     }
 
     public static Surface Load(string path)
     {
         if (!File.Exists(path))
             throw new FileNotFoundException(null, path);
-        using var image = SKImage.FromEncodedData(path);
+        using var image = Image.FromEncodedData(path);
         if (image is null)
             throw new ArgumentException($"The image with path {path} couldn't be loaded");
 
         var surface = new Surface(new VecI(image.Width, image.Height));
-        surface.SkiaSurface.Canvas.DrawImage(image, 0, 0);
+        surface.DrawingSurface.Canvas.DrawImage(image, 0, 0);
 
         return surface;
     }
 
-    public unsafe void DrawBytes(VecI size, byte[] bytes, SKColorType colorType, SKAlphaType alphaType)
+    public unsafe void DrawBytes(VecI size, byte[] bytes, ColorType colorType, AlphaType alphaType)
     {
-        SKImageInfo info = new SKImageInfo(size.X, size.Y, colorType, alphaType);
+        ImageInfo info = new ImageInfo(size.X, size.Y, colorType, alphaType);
 
         fixed (void* pointer = bytes)
         {
-            using SKPixmap map = new(info, new IntPtr(pointer));
-            using SKSurface surface = SKSurface.Create(map);
-            surface.Draw(SkiaSurface.Canvas, 0, 0, drawingPaint);
+            using Pixmap map = new(info, new IntPtr(pointer));
+            using DrawingSurface surface = DrawingSurface.Create(map);
+            surface.Draw(DrawingSurface.Canvas, 0, 0, drawingPaint);
         }
     }
 
     public Surface ResizeNearestNeighbor(VecI newSize)
     {
-        using SKImage image = SkiaSurface.Snapshot();
+        using Image image = DrawingSurface.Snapshot();
         Surface newSurface = new(newSize);
-        newSurface.SkiaSurface.Canvas.DrawImage(image, new SKRect(0, 0, newSize.X, newSize.Y), nearestNeighborReplacingPaint);
+        newSurface.DrawingSurface.Canvas.DrawImage(image, new RectD(0, 0, newSize.X, newSize.Y), nearestNeighborReplacingPaint);
         return newSurface;
     }
 
@@ -74,24 +77,24 @@ public class Surface : IDisposable
         if (other.Size != Size)
             throw new ArgumentException("Target Surface must have the same dimensions");
         int bytesC = Size.X * Size.Y * BytesPerPixel;
-        using var pixmap = other.SkiaSurface.PeekPixels();
+        using var pixmap = other.DrawingSurface.PeekPixels();
         Buffer.MemoryCopy((void*)PixelBuffer, (void*)pixmap.GetPixels(), bytesC, bytesC);
     }
 
     /// <summary>
     /// Consider getting a pixmap from SkiaSurface.PeekPixels().GetPixels() and writing into it's buffer for bulk pixel get/set. Don't forget to dispose the pixmap afterwards.
     /// </summary>
-    public unsafe SKColor GetSRGBPixel(VecI pos)
+    public unsafe Color GetSRGBPixel(VecI pos)
     {
         Half* ptr = (Half*)(PixelBuffer + (pos.X + pos.Y * Size.X) * BytesPerPixel);
         float a = (float)ptr[3];
-        return (SKColor)new SKColorF((float)ptr[0] / a, (float)ptr[1] / a, (float)ptr[2] / a, (float)ptr[3]);
+        return (Color)new ColorF((float)ptr[0] / a, (float)ptr[1] / a, (float)ptr[2] / a, (float)ptr[3]);
     }
 
-    public void SetSRGBPixel(VecI pos, SKColor color)
+    public void SetSRGBPixel(VecI pos, Color color)
     {
         drawingPaint.Color = color;
-        SkiaSurface.Canvas.DrawPoint(pos.X, pos.Y, drawingPaint);
+        DrawingSurface.Canvas.DrawPixel(pos.X, pos.Y, drawingPaint);
     }
 
     public unsafe bool IsFullyTransparent()
@@ -109,8 +112,8 @@ public class Surface : IDisposable
 
     public void SaveToDesktop(string filename = "savedSurface.png")
     {
-        using var final = SKSurface.Create(new SKImageInfo(Size.X, Size.Y, SKColorType.Rgba8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb()));
-        final.Canvas.DrawSurface(SkiaSurface, 0, 0);
+        using var final = DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.Rgba8888, AlphaType.Premul, ColorSpace.CreateSrgb()));
+        final.Canvas.DrawSurface(DrawingSurface, 0, 0);
         using (var snapshot = final.Snapshot())
         {
             using var stream = File.Create(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), filename));
@@ -119,9 +122,9 @@ public class Surface : IDisposable
         }
     }
 
-    private SKSurface CreateSKSurface()
+    private DrawingSurface CreateDrawingSurface()
     {
-        var surface = SKSurface.Create(new SKImageInfo(Size.X, Size.Y, SKColorType.RgbaF16, SKAlphaType.Premul, SKColorSpace.CreateSrgb()), PixelBuffer);
+        var surface = PixiEditor.DrawingApi.Core.Surface.DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.RgbaF16, AlphaType.Premul, ColorSpace.CreateSrgb()), PixelBuffer);
         if (surface is null)
             throw new InvalidOperationException($"Could not create surface (Size:{Size})");
         return surface;

+ 26 - 26
src/ChunkyImageLibTest/ChunkyImageTests.cs

@@ -1,8 +1,8 @@
-using System;
-using System.IO;
-using ChunkyImageLib;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 using Xunit;
 
 namespace ChunkyImageLibTest;
@@ -24,30 +24,30 @@ public class ChunkyImageTests
     public void LoadDemo()
     {
         var path = @"C:\Users\egor0\Desktop\SpazzS1.png";
-        ImportImage(path, new VecI(5,5));
+        ImportImage(path, new VecI(5, 5));
     }
 
     [Fact]
     public void Dispose_ComplexImage_ReturnsAllChunks()
     {
         ChunkyImage image = new ChunkyImage(new(ChunkyImage.FullChunkSize, ChunkyImage.FullChunkSize));
-        image.EnqueueDrawRectangle(new(new(5, 5), new(80, 80), 0, 2, SKColors.AliceBlue, SKColors.Snow));
+        image.EnqueueDrawRectangle(new(new(5, 5), new(80, 80), 0, 2, Colors.AliceBlue, Colors.Snow));
         using (Chunk target = Chunk.Create())
         {
-            image.DrawMostUpToDateChunkOn(new(0, 0), ChunkResolution.Full, target.Surface.SkiaSurface, VecI.Zero);
+            image.DrawMostUpToDateChunkOn(new(0, 0), ChunkResolution.Full, target.Surface.DrawingSurface, VecI.Zero);
             image.CancelChanges();
             image.EnqueueResize(new(ChunkyImage.FullChunkSize * 4, ChunkyImage.FullChunkSize * 4));
-            image.EnqueueDrawRectangle(new(VecD.Zero, image.CommittedSize, 0, 2, SKColors.AliceBlue, SKColors.Snow, SKBlendMode.Multiply));
+            image.EnqueueDrawRectangle(new(VecD.Zero, image.CommittedSize, 0, 2, Colors.AliceBlue, Colors.Snow, BlendMode.Multiply));
             image.CommitChanges();
-            image.SetBlendMode(SKBlendMode.Overlay);
-            image.EnqueueDrawRectangle(new(VecD.Zero, image.CommittedSize, 0, 2, SKColors.AliceBlue, SKColors.Snow, SKBlendMode.Multiply));
-            image.EnqueueDrawRectangle(new(VecD.Zero, image.CommittedSize, 0, 2, SKColors.AliceBlue, SKColors.Snow));
+            image.SetBlendMode(BlendMode.Overlay);
+            image.EnqueueDrawRectangle(new(VecD.Zero, image.CommittedSize, 0, 2, Colors.AliceBlue, Colors.Snow, BlendMode.Multiply));
+            image.EnqueueDrawRectangle(new(VecD.Zero, image.CommittedSize, 0, 2, Colors.AliceBlue, Colors.Snow));
             image.CommitChanges();
-            image.SetBlendMode(SKBlendMode.Screen);
-            image.EnqueueDrawRectangle(new(VecD.Zero, image.CommittedSize, 0, 2, SKColors.AliceBlue, SKColors.Snow));
+            image.SetBlendMode(BlendMode.Screen);
+            image.EnqueueDrawRectangle(new(VecD.Zero, image.CommittedSize, 0, 2, Colors.AliceBlue, Colors.Snow));
             image.CancelChanges();
-            image.SetBlendMode(SKBlendMode.SrcOver);
-            image.EnqueueDrawRectangle(new(VecD.Zero, image.CommittedSize, 0, 2, SKColors.AliceBlue, SKColors.Snow));
+            image.SetBlendMode(BlendMode.SrcOver);
+            image.EnqueueDrawRectangle(new(VecD.Zero, image.CommittedSize, 0, 2, Colors.AliceBlue, Colors.Snow));
         }
         image.Dispose();
 
@@ -60,9 +60,9 @@ public class ChunkyImageTests
         const int chunkSize = ChunkyImage.FullChunkSize;
         ChunkyImage image = new ChunkyImage(new VecI(chunkSize * 2));
         image.EnqueueDrawRectangle
-            (new ShapeData(new VecD(chunkSize), new VecD(chunkSize * 2), 0, 0, SKColors.Transparent, SKColors.Red));
+            (new ShapeData(new VecD(chunkSize), new VecD(chunkSize * 2), 0, 0, Colors.Transparent, Colors.Red));
         image.CommitChanges();
-        Assert.Equal(SKColors.Red, image.GetCommittedPixel(new VecI(chunkSize + chunkSize / 2)));
+        Assert.Equal(Colors.Red, image.GetCommittedPixel(new VecI(chunkSize + chunkSize / 2)));
         image.Dispose();
         Assert.Equal(0, Chunk.ChunkCounter);
     }
@@ -73,8 +73,8 @@ public class ChunkyImageTests
         const int chunkSize = ChunkyImage.FullChunkSize;
         ChunkyImage image = new ChunkyImage(new VecI(chunkSize * 2));
         image.EnqueueDrawRectangle
-            (new ShapeData(new VecD(chunkSize), new VecD(chunkSize * 2), 0, 0, SKColors.Transparent, SKColors.Red));
-        Assert.Equal(SKColors.Red, image.GetMostUpToDatePixel(new VecI(chunkSize + chunkSize / 2)));
+            (new ShapeData(new VecD(chunkSize), new VecD(chunkSize * 2), 0, 0, Colors.Transparent, Colors.Red));
+        Assert.Equal(Colors.Red, image.GetMostUpToDatePixel(new VecI(chunkSize + chunkSize / 2)));
         image.Dispose();
         Assert.Equal(0, Chunk.ChunkCounter);
     }
@@ -85,17 +85,17 @@ public class ChunkyImageTests
         const int chunkSize = ChunkyImage.FullChunkSize;
         ChunkyImage image = new ChunkyImage(new VecI(chunkSize * 2));
         image.EnqueueDrawRectangle
-            (new ShapeData(new VecD(chunkSize), new VecD(chunkSize * 2), 0, 0, SKColors.Transparent, SKColors.Red));
+            (new ShapeData(new VecD(chunkSize), new VecD(chunkSize * 2), 0, 0, Colors.Transparent, Colors.Red));
         image.CommitChanges();
-        image.SetBlendMode(SKBlendMode.SrcOver);
+        image.SetBlendMode(BlendMode.SrcOver);
         image.EnqueueDrawRectangle(new ShapeData(
             new VecD(chunkSize),
             new VecD(chunkSize * 2),
             0, 
             0,
-            SKColors.Transparent,
-            new SKColor(0, 255, 0, 128)));
-        Assert.Equal(new SKColor(127, 128, 0), image.GetMostUpToDatePixel(new VecI(chunkSize + chunkSize / 2)));
+            Colors.Transparent,
+            new Color(0, 255, 0, 128)));
+        Assert.Equal(new Color(127, 128, 0), image.GetMostUpToDatePixel(new VecI(chunkSize + chunkSize / 2)));
         image.Dispose();
         Assert.Equal(0, Chunk.ChunkCounter);
     }
@@ -110,8 +110,8 @@ public class ChunkyImageTests
                 new VecD(chunkSize * 10),
                 0,
                 0,
-                SKColors.Transparent,
-                SKColors.Red));
+                Colors.Transparent,
+                Colors.Red));
         image.CommitChanges();
         Assert.Collection(
             image.FindAllChunks(), 

+ 1 - 0
src/ChunkyImageLibTest/ClearRegionOperationTests.cs

@@ -2,6 +2,7 @@
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
+using PixiEditor.DrawingApi.Core.Numerics;
 using Xunit;
 
 namespace ChunkyImageLibTest;

+ 1 - 0
src/ChunkyImageLibTest/ImageOperationTests.cs

@@ -2,6 +2,7 @@
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
+using PixiEditor.DrawingApi.Core.Numerics;
 using Xunit;
 
 namespace ChunkyImageLibTest;

+ 1 - 0
src/ChunkyImageLibTest/OperationHelperTests.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
+using PixiEditor.DrawingApi.Core.Numerics;
 using Xunit;
 
 namespace ChunkyImageLibTest;

+ 1 - 0
src/ChunkyImageLibTest/RectITests.cs

@@ -1,5 +1,6 @@
 using System;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core.Numerics;
 using Xunit;
 
 namespace ChunkyImageLibTest;

+ 9 - 9
src/ChunkyImageLibTest/RectangleOperationTests.cs

@@ -1,8 +1,8 @@
 using System.Collections.Generic;
 using ChunkyImageLib;
-using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
 using Xunit;
 
 namespace ChunkyImageLibTest;
@@ -16,7 +16,7 @@ public class RectangleOperationTests
     public void FindAffectedChunks_SmallStrokeOnly_FindsCorrectChunks()
     {
         var (x, y, w, h) = (chunkSize / 2, chunkSize / 2, chunkSize, chunkSize);
-        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, 1, SKColors.Black, SKColors.Transparent));
+        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, 1, Colors.Black, Colors.Transparent));
 
         HashSet<VecI> expected = new() { new(0, 0) };
         var actual = operation.FindAffectedChunks(new(chunkSize));
@@ -28,7 +28,7 @@ public class RectangleOperationTests
     public void FindAffectedChunks_2by2StrokeOnly_FindsCorrectChunks()
     {
         var (x, y, w, h) = (0, 0, chunkSize * 2, chunkSize * 2);
-        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, 1, SKColors.Black, SKColors.Transparent));
+        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, 1, Colors.Black, Colors.Transparent));
 
         HashSet<VecI> expected = new() { new(-1, -1), new(0, -1), new(-1, 0), new(0, 0) };
         var actual = operation.FindAffectedChunks(new(chunkSize));
@@ -40,7 +40,7 @@ public class RectangleOperationTests
     public void FindAffectedChunks_3x3PositiveStrokeOnly_FindsCorrectChunks()
     {
         var (x, y, w, h) = (2 * chunkSize + chunkSize / 2, 2 * chunkSize + chunkSize / 2, chunkSize * 2, chunkSize * 2);
-        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, 1, SKColors.Black, SKColors.Transparent));
+        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, 1, Colors.Black, Colors.Transparent));
 
         HashSet<VecI> expected = new()
         {
@@ -57,7 +57,7 @@ public class RectangleOperationTests
     public void FindAffectedChunks_3x3NegativeStrokeOnly_FindsCorrectChunks()
     {
         var (x, y, w, h) = (-chunkSize * 2 - chunkSize / 2, -chunkSize * 2 - chunkSize / 2, chunkSize * 2, chunkSize * 2);
-        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, 1, SKColors.Black, SKColors.Transparent));
+        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, 1, Colors.Black, Colors.Transparent));
 
         HashSet<VecI> expected = new()
         {
@@ -74,7 +74,7 @@ public class RectangleOperationTests
     public void FindAffectedChunks_3x3PositiveFilled_FindsCorrectChunks()
     {
         var (x, y, w, h) = (2 * chunkSize + chunkSize / 2, 2 * chunkSize + chunkSize / 2, chunkSize * 2, chunkSize * 2);
-        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, 1, SKColors.Black, SKColors.White));
+        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, 1, Colors.Black, Colors.White));
 
         HashSet<VecI> expected = new()
         {
@@ -91,7 +91,7 @@ public class RectangleOperationTests
     public void FindAffectedChunks_ThickPositiveStroke_FindsCorrectChunks()
     {
         var (x, y, w, h) = (2 * chunkSize + chunkSize / 2, 2 * chunkSize + chunkSize / 2, chunkSize * 4, chunkSize * 4);
-        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, chunkSize, SKColors.Black, SKColors.Transparent));
+        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, chunkSize, Colors.Black, Colors.Transparent));
 
         HashSet<VecI> expected = new()
         {
@@ -110,7 +110,7 @@ public class RectangleOperationTests
     public void FindAffectedChunks_SmallButThick_FindsCorrectChunks()
     {
         var (x, y, w, h) = (chunkSize / 2f - 0.5, chunkSize / 2f - 0.5, 1, 1);
-        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, chunkSize, SKColors.Black, SKColors.White));
+        RectangleOperation operation = new(new(new(x, y), new(w, h), 0, chunkSize, Colors.Black, Colors.White));
 
         HashSet<VecI> expected = new() { new(0, 0) };
         var actual = operation.FindAffectedChunks(new(chunkSize));

+ 1 - 0
src/ChunkyImageLibVis/MainWindow.xaml.cs

@@ -8,6 +8,7 @@ using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Shapes;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace ChunkyImageLibVis;
 

+ 3 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/LayerImageChunks_ChangeInfo.cs

@@ -1,3 +1,5 @@
-namespace PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 
 public record class LayerImageChunks_ChangeInfo(Guid GuidValue, HashSet<VecI> Chunks) : IChangeInfo;

+ 3 - 1
src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/MaskChunks_ChangeInfo.cs

@@ -1,3 +1,5 @@
-namespace PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 
 public record class MaskChunks_ChangeInfo(Guid GuidValue, HashSet<VecI> Chunks) : IChangeInfo;

+ 2 - 2
src/PixiEditor.ChangeableDocument/ChangeInfos/Drawing/Selection_ChangeInfo.cs

@@ -1,5 +1,5 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 
-public record class Selection_ChangeInfo(SKPath NewPath) : IChangeInfo;
+public record class Selection_ChangeInfo(VectorPath NewPath) : IChangeInfo;

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

@@ -1,3 +1,5 @@
-namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Root;
 
 public record class Size_ChangeInfo(VecI Size, int VerticalSymmetryAxisX, int HorizontalSymmetryAxisY) : IChangeInfo;

+ 2 - 3
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -1,7 +1,6 @@
-using System.Collections.Immutable;
-using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.CodeAnalysis;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables;
 

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

@@ -1,6 +1,5 @@
-using System.Collections.Immutable;
-using System.Diagnostics.CodeAnalysis;
-using SkiaSharp;
+using System.Diagnostics.CodeAnalysis;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 

+ 2 - 2
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlySelection.cs

@@ -1,4 +1,4 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
@@ -7,5 +7,5 @@ public interface IReadOnlySelection
     /// <summary>
     /// The path of the selection
     /// </summary>
-    public SKPath SelectionPath { get; }
+    public VectorPath SelectionPath { get; }
 }

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changeables/Layer.cs

@@ -1,4 +1,5 @@
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables;
 

+ 0 - 1
src/PixiEditor.ChangeableDocument/Changeables/ReferenceLayer.cs

@@ -1,5 +1,4 @@
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using SkiaSharp;
 
 namespace PixiEditor.ChangeableDocument.Changeables;
 

+ 9 - 7
src/PixiEditor.ChangeableDocument/Changeables/Selection.cs

@@ -1,23 +1,25 @@
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changeables;
 
 internal class Selection : IReadOnlySelection, IDisposable
 {
-    public static SKColor SelectionColor { get; } = SKColors.CornflowerBlue;
-    public SKPath SelectionPath { get; set; } = new();
-    SKPath IReadOnlySelection.SelectionPath 
+    public static Color SelectionColor { get; } = Colors.CornflowerBlue;
+    public VectorPath SelectionPath { get; set; } = new();
+    VectorPath IReadOnlySelection.SelectionPath 
     {
-        get {
+        get 
+        {
             try
             {
                 // I think this might throw if another thread disposes SelectionPath at the wrong time?
-                return new SKPath(SelectionPath) { FillType = SKPathFillType.EvenOdd };
+                return new VectorPath(SelectionPath) { FillType = PathFillType.EvenOdd };
             }
             catch (Exception)
             {
-                return new SKPath();
+                return new VectorPath();
             }
         }
     }

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/ApplyLayerMask_Change.cs

@@ -1,4 +1,5 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal class ApplyLayerMask_Change : Change

+ 12 - 9
src/PixiEditor.ChangeableDocument/Changes/Drawing/ChangeBrightness_UpdateableChange.cs

@@ -1,4 +1,7 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 
@@ -10,8 +13,8 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
     private bool ignoreUpdate = false;
     private readonly bool repeat;
     private readonly bool darken;
-    private readonly SKPaint paint;
-    private readonly SKColor color;
+    private readonly Paint paint;
+    private readonly Color color;
     private CommittedChunkStorage? savedChunks;
 
     [GenerateUpdateableChangeActions]
@@ -23,9 +26,9 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
         this.repeat = repeat;
         this.darken = darken;
 
-        color = (darken ? SKColors.Black : SKColors.White)
+        color = (darken ? Colors.Black : Colors.White)
             .WithAlpha((byte)Math.Clamp(correctionFactor * 255 / 100, 0, 255)); 
-        paint = new SKPaint { BlendMode = repeat ? SKBlendMode.SrcOver : SKBlendMode.Src };
+        paint = new Paint { BlendMode = repeat ? BlendMode.SrcOver : BlendMode.Src };
     }
 
     [UpdateChangeMethod]
@@ -42,7 +45,7 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
             return new Error();
         Layer layer = (Layer)target.FindMemberOrThrow(layerGuid);
         DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, layer.LayerImage, layerGuid, false);
-        layer.LayerImage.SetBlendMode(darken ? SKBlendMode.Multiply : SKBlendMode.Screen);
+        layer.LayerImage.SetBlendMode(darken ? BlendMode.Multiply : BlendMode.Screen);
         return new Success();
     }
 
@@ -56,7 +59,7 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
         int queueLength = layer.LayerImage.QueueLength;
         layer.LayerImage.EnqueueDrawEllipse(
             new RectI(pos + new VecI(-strokeWidth/2), new(strokeWidth)),
-            SKColors.Transparent, color, 0, paint);
+            Colors.Transparent, color, 0, paint);
         var affected = layer.LayerImage.FindAffectedChunks(queueLength);
         
         return new LayerImageChunks_ChangeInfo(layerGuid, affected);
@@ -73,12 +76,12 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
         if (!firstApply)
         {
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, layer.LayerImage, layerGuid, false);
-            layer.LayerImage.SetBlendMode(darken ? SKBlendMode.Multiply : SKBlendMode.Screen);
+            layer.LayerImage.SetBlendMode(darken ? BlendMode.Multiply : BlendMode.Screen);
             foreach (VecI pos in positions)
             {
                 layer.LayerImage.EnqueueDrawEllipse(
                     new RectI(pos + new VecI(-strokeWidth/2), new(strokeWidth)),
-                    SKColors.Transparent, color, 0, paint);
+                    Colors.Transparent, color, 0, paint);
             }
         }
 

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

@@ -1,4 +1,4 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal class ClearSelectedArea_Change : Change
@@ -31,7 +31,7 @@ internal class ClearSelectedArea_Change : Change
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
 
         RectD bounds = target.Selection.SelectionPath.Bounds;
-        RectI intBounds = (RectI)bounds.Intersect(SKRect.Create(0, 0, target.Size.X, target.Size.Y)).RoundOutwards();
+        RectI intBounds = (RectI)bounds.Intersect(new RectD(0, 0, target.Size.X, target.Size.Y)).RoundOutwards();
 
         image.EnqueueClearPath(target.Selection.SelectionPath, intBounds);
         var affChunks = image.FindAffectedChunks();

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/CombineStructureMembersOnto_Change.cs

@@ -1,4 +1,5 @@
 using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 

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

@@ -1,19 +1,20 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal class DrawEllipse_UpdateableChange : UpdateableChange
 {
     private readonly Guid memberGuid;
     private RectI location;
-    private readonly SKColor strokeColor;
-    private readonly SKColor fillColor;
+    private readonly Color strokeColor;
+    private readonly Color fillColor;
     private readonly int strokeWidth;
     private readonly bool drawOnMask;
 
     private CommittedChunkStorage? storedChunks;
 
     [GenerateUpdateableChangeActions]
-    public DrawEllipse_UpdateableChange(Guid memberGuid, RectI location, SKColor strokeColor, SKColor fillColor, int strokeWidth, bool drawOnMask)
+    public DrawEllipse_UpdateableChange(Guid memberGuid, RectI location, Color strokeColor, Color fillColor, int strokeWidth, bool drawOnMask)
     {
         this.memberGuid = memberGuid;
         this.location = location;

+ 9 - 7
src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawLine_UpdateableChange.cs

@@ -1,4 +1,6 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 
@@ -8,14 +10,14 @@ internal class DrawLine_UpdateableChange : UpdateableChange
     private VecI from;
     private VecI to;
     private int strokeWidth;
-    private SKColor color;
-    private SKStrokeCap caps;
+    private Color color;
+    private StrokeCap caps;
     private readonly bool drawOnMask;
     private CommittedChunkStorage? savedChunks;
 
     [GenerateUpdateableChangeActions]
     public DrawLine_UpdateableChange
-        (Guid memberGuid, VecI from, VecI to, int strokeWidth, SKColor color, SKStrokeCap caps, bool drawOnMask)
+        (Guid memberGuid, VecI from, VecI to, int strokeWidth, Color color, StrokeCap caps, bool drawOnMask)
     {
         this.memberGuid = memberGuid;
         this.from = from;
@@ -27,7 +29,7 @@ internal class DrawLine_UpdateableChange : UpdateableChange
     }
 
     [UpdateChangeMethod]
-    public void Update(VecI from, VecI to, int strokeWidth, SKColor color, SKStrokeCap caps)
+    public void Update(VecI from, VecI to, int strokeWidth, Color color, StrokeCap caps)
     {
         this.from = from;
         this.to = to;
@@ -52,9 +54,9 @@ internal class DrawLine_UpdateableChange : UpdateableChange
         {
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
             if (strokeWidth == 1)
-                image.EnqueueDrawBresenhamLine(from, to, color, SKBlendMode.SrcOver);
+                image.EnqueueDrawBresenhamLine(from, to, color, BlendMode.SrcOver);
             else
-                image.EnqueueDrawSkiaLine(from, to, caps, strokeWidth, color, SKBlendMode.SrcOver);
+                image.EnqueueDrawSkiaLine(from, to, caps, strokeWidth, color, BlendMode.SrcOver);
         }
         var totalAffected = image.FindAffectedChunks();
         totalAffected.UnionWith(oldAffected);

+ 3 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawRectangle_UpdateableChange.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.ChangeableDocument.Changes.Drawing;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 
 internal class DrawRectangle_UpdateableChange : UpdateableChange
 {

+ 3 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/DrawingChangeHelper.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.ChangeableDocument.Changes.Drawing;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal static class DrawingChangeHelper
 {
     public static HashSet<VecI> ApplyStoredChunksDisposeAndSetToNull(Document target, Guid memberGuid, bool drawOnMask, ref CommittedChunkStorage? storage)

+ 5 - 3
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkCache.cs

@@ -1,12 +1,14 @@
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
 
 internal class FloodFillChunkCache : IDisposable
 {
-    private SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
+    private Paint ReplacingPaint { get; } = new Paint() { BlendMode = BlendMode.Src };
 
     private readonly HashSet<Guid>? membersToRender;
     private readonly IReadOnlyFolder? structureRoot;
@@ -53,7 +55,7 @@ internal class FloodFillChunkCache : IDisposable
             return new EmptyChunk();
         Chunk chunkOnImage = Chunk.Create(ChunkResolution.Full);
 
-        if (!image.DrawMostUpToDateChunkOn(pos, ChunkResolution.Full, chunkOnImage.Surface.SkiaSurface, VecI.Zero, ReplacingPaint))
+        if (!image.DrawMostUpToDateChunkOn(pos, ChunkResolution.Full, chunkOnImage.Surface.DrawingSurface, VecI.Zero, ReplacingPaint))
         {
             chunkOnImage.Dispose();
             acquiredChunks[pos] = new EmptyChunk();

+ 23 - 20
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs

@@ -1,8 +1,11 @@
 using System.Runtime.CompilerServices;
 using ChunkyImageLib.Operations;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using SkiaSharp;
-using ChunkyImageLib;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
 
@@ -27,9 +30,9 @@ internal static class FloodFillHelper
     public static Dictionary<VecI, Chunk> FloodFill(
         HashSet<Guid> membersToFloodFill,
         IReadOnlyDocument document,
-        SKPath? selection,
+        VectorPath? selection,
         VecI startingPos,
-        SKColor drawingColor)
+        Color drawingColor)
     {
         int chunkSize = ChunkResolution.Full.PixelSize();
 
@@ -38,12 +41,12 @@ internal static class FloodFillHelper
         VecI initChunkPos = OperationHelper.GetChunkPos(startingPos, chunkSize);
         VecI imageSizeInChunks = (VecI)(document.Size / (double)chunkSize).Ceiling();
         VecI initPosOnChunk = startingPos - initChunkPos * chunkSize;
-        SKColor colorToReplace = cache.GetChunk(initChunkPos).Match(
+        Color colorToReplace = cache.GetChunk(initChunkPos).Match(
             (Chunk chunk) => chunk.Surface.GetSRGBPixel(initPosOnChunk),
-            static (EmptyChunk _) => SKColors.Transparent
+            static (EmptyChunk _) => Colors.Transparent
         );
 
-        if ((colorToReplace.Alpha == 0 && drawingColor.Alpha == 0) || colorToReplace == drawingColor)
+        if ((colorToReplace.A == 0 && drawingColor.A == 0) || colorToReplace == drawingColor)
             return new();
 
         RectI globalSelectionBounds = (RectI?)selection?.TightBounds ?? new RectI(VecI.Zero, document.Size);
@@ -66,7 +69,7 @@ internal static class FloodFillHelper
             if (!drawingChunks.ContainsKey(chunkPos))
             {
                 var chunk = Chunk.Create();
-                chunk.Surface.SkiaSurface.Canvas.Clear(SKColors.Transparent);
+                chunk.Surface.DrawingSurface.Canvas.Clear(Colors.Transparent);
                 drawingChunks[chunkPos] = chunk;
             }
             var drawingChunk = drawingChunks[chunkPos];
@@ -75,9 +78,9 @@ internal static class FloodFillHelper
             // don't call floodfill if the chunk is empty
             if (referenceChunk.IsT1)
             {
-                if (colorToReplace.Alpha == 0 && !processedEmptyChunks.Contains(chunkPos))
+                if (colorToReplace.A == 0 && !processedEmptyChunks.Contains(chunkPos))
                 {
-                    drawingChunk.Surface.SkiaSurface.Canvas.Clear(drawingColor);
+                    drawingChunk.Surface.DrawingSurface.Canvas.Clear(drawingColor);
                     for (int i = 0; i < chunkSize; i++)
                     {
                         if (chunkPos.Y > 0)
@@ -128,12 +131,12 @@ internal static class FloodFillHelper
     private static unsafe byte[]? FloodFillChunk(
         Chunk referenceChunk,
         Chunk drawingChunk,
-        SKPath? selection,
+        VectorPath? selection,
         RectI globalSelectionBounds,
         VecI chunkPos,
         int chunkSize,
         ulong colorBits,
-        SKColor color,
+        Color color,
         VecI pos,
         ColorBounds bounds)
     {
@@ -143,10 +146,10 @@ internal static class FloodFillHelper
         byte[] pixelStates = new byte[chunkSize * chunkSize];
         DrawSelection(pixelStates, selection, globalSelectionBounds, chunkPos, chunkSize);
 
-        using var refPixmap = referenceChunk.Surface.SkiaSurface.PeekPixels();
+        using var refPixmap = referenceChunk.Surface.DrawingSurface.PeekPixels();
         Half* refArray = (Half*)refPixmap.GetPixels();
 
-        using var drawPixmap = drawingChunk.Surface.SkiaSurface.PeekPixels();
+        using var drawPixmap = drawingChunk.Surface.DrawingSurface.PeekPixels();
         Half* drawArray = (Half*)drawPixmap.GetPixels();
 
         Stack<VecI> toVisit = new();
@@ -176,7 +179,7 @@ internal static class FloodFillHelper
     /// <summary>
     /// Use skia to set all pixels in array that are inside selection to InSelection
     /// </summary>
-    private static unsafe void DrawSelection(byte[] array, SKPath? selection, RectI globalBounds, VecI chunkPos, int chunkSize)
+    private static unsafe void DrawSelection(byte[] array, VectorPath? selection, RectI globalBounds, VecI chunkPos, int chunkSize)
     {
         if (selection is null)
         {
@@ -190,15 +193,15 @@ internal static class FloodFillHelper
         RectI localBounds = globalBounds.Offset(-chunkPos * chunkSize).Intersect(new(0, 0, chunkSize, chunkSize));
         if (localBounds.IsZeroOrNegativeArea)
             return;
-        SKPath shiftedSelection = new SKPath(selection);
-        shiftedSelection.Transform(SKMatrix.CreateTranslation(-chunkPos.X * chunkSize, -chunkPos.Y * chunkSize));
+        VectorPath shiftedSelection = new VectorPath(selection);
+        shiftedSelection.Transform(Matrix3X3.CreateTranslation(-chunkPos.X * chunkSize, -chunkPos.Y * chunkSize));
 
         fixed (byte* arr = array)
         {
-            using SKSurface drawingSurface = SKSurface.Create(
-                new SKImageInfo(localBounds.Right, localBounds.Bottom, SKColorType.Gray8, SKAlphaType.Opaque), (IntPtr)arr, chunkSize);
+            using DrawingSurface drawingSurface = DrawingSurface.Create(
+                new ImageInfo(localBounds.Right, localBounds.Bottom, ColorType.Gray8, AlphaType.Opaque), (IntPtr)arr, chunkSize);
             drawingSurface.Canvas.ClipPath(shiftedSelection);
-            drawingSurface.Canvas.Clear(new SKColor(InSelection, InSelection, InSelection));
+            drawingSurface.Canvas.Clear(new Color(InSelection, InSelection, InSelection));
             drawingSurface.Canvas.Flush();
         }
     }

+ 6 - 4
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFill_Change.cs

@@ -1,4 +1,6 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing.FloodFill;
 
@@ -6,13 +8,13 @@ internal class FloodFill_Change : Change
 {
     private readonly Guid memberGuid;
     private readonly VecI pos;
-    private readonly SKColor color;
+    private readonly Color color;
     private readonly bool referenceAll;
     private readonly bool drawOnMask;
     private CommittedChunkStorage? chunkStorage = null;
 
     [GenerateMakeChangeAction]
-    public FloodFill_Change(Guid memberGuid, VecI pos, SKColor color, bool referenceAll, bool drawOnMask)
+    public FloodFill_Change(Guid memberGuid, VecI pos, Color color, bool referenceAll, bool drawOnMask)
     {
         this.memberGuid = memberGuid;
         this.pos = pos;
@@ -34,7 +36,7 @@ internal class FloodFill_Change : Change
     {
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
 
-        SKPath? selection = target.Selection.SelectionPath.IsEmpty ? null : target.Selection.SelectionPath;
+        VectorPath? selection = target.Selection.SelectionPath.IsEmpty ? null : target.Selection.SelectionPath;
         HashSet<Guid> membersToReference = new();
         if (referenceAll)
             target.ForEveryReadonlyMember(member => membersToReference.Add(member.GuidValue));

+ 14 - 11
src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs

@@ -1,20 +1,23 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal class LineBasedPen_UpdateableChange : UpdateableChange
 {
     private readonly Guid memberGuid;
-    private readonly SKColor color;
+    private readonly Color color;
     private readonly int strokeWidth;
     private readonly bool replacing;
     private readonly bool drawOnMask;
-    private readonly SKPaint srcPaint = new SKPaint() { BlendMode = SKBlendMode.Src };
+    private readonly Paint srcPaint = new Paint() { BlendMode = BlendMode.Src };
 
     private CommittedChunkStorage? storedChunks;
     private readonly List<VecI> points = new();
 
     [GenerateUpdateableChangeActions]
-    public LineBasedPen_UpdateableChange(Guid memberGuid, SKColor color, VecI pos, int strokeWidth, bool replacing, bool drawOnMask)
+    public LineBasedPen_UpdateableChange(Guid memberGuid, Color color, VecI pos, int strokeWidth, bool replacing, bool drawOnMask)
     {
         this.memberGuid = memberGuid;
         this.color = color;
@@ -38,7 +41,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
             return new Error();
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
         if (!replacing)
-            image.SetBlendMode(SKBlendMode.SrcOver);
+            image.SetBlendMode(BlendMode.SrcOver);
         DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
         return new Success();
     }
@@ -53,13 +56,13 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
 
         if (strokeWidth == 1)
         {
-            image.EnqueueDrawBresenhamLine(from, to, color, SKBlendMode.Src);
+            image.EnqueueDrawBresenhamLine(from, to, color, BlendMode.Src);
         }
         else
         {
             var rect = new RectI(to - new VecI(strokeWidth / 2), new VecI(strokeWidth));
             image.EnqueueDrawEllipse(rect, color, color, 1, srcPaint);
-            image.EnqueueDrawSkiaLine(from, to, SKStrokeCap.Butt, strokeWidth, color, SKBlendMode.Src);
+            image.EnqueueDrawSkiaLine(from, to, StrokeCap.Butt, strokeWidth, color, BlendMode.Src);
         }
         var affChunks = image.FindAffectedChunks(opCount);
 
@@ -72,7 +75,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         {
             if (strokeWidth == 1)
             {
-                targetImage.EnqueueDrawBresenhamLine(points[0], points[0], color, SKBlendMode.Src);
+                targetImage.EnqueueDrawBresenhamLine(points[0], points[0], color, BlendMode.Src);
             }
             else
             {
@@ -89,13 +92,13 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         {
             if (strokeWidth == 1)
             {
-                targetImage.EnqueueDrawBresenhamLine(points[i - 1], points[i], color, SKBlendMode.Src);
+                targetImage.EnqueueDrawBresenhamLine(points[i - 1], points[i], color, BlendMode.Src);
             }
             else
             {
                 var rect = new RectI(points[i] - new VecI(strokeWidth / 2), new VecI(strokeWidth));
                 targetImage.EnqueueDrawEllipse(rect, color, color, 1, srcPaint);
-                targetImage.EnqueueDrawSkiaLine(points[i - 1], points[i], SKStrokeCap.Butt, strokeWidth, color, SKBlendMode.Src);
+                targetImage.EnqueueDrawSkiaLine(points[i - 1], points[i], StrokeCap.Butt, strokeWidth, color, BlendMode.Src);
             }
         }
     }
@@ -118,7 +121,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         else
         {
             if (!replacing)
-                image.SetBlendMode(SKBlendMode.SrcOver);
+                image.SetBlendMode(BlendMode.SrcOver);
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
 
             FastforwardEnqueueDrawLines(image);

+ 4 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/PasteImage_UpdateableChange.cs

@@ -1,4 +1,6 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal class PasteImage_UpdateableChange : UpdateableChange
@@ -9,7 +11,7 @@ internal class PasteImage_UpdateableChange : UpdateableChange
     private readonly bool drawOnMask;
     private readonly Surface imageToPaste;
     private CommittedChunkStorage? savedChunks;
-    private static SKPaint RegularPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
+    private static Paint RegularPaint { get; } = new Paint() { BlendMode = BlendMode.SrcOver };
 
     private bool hasEnqueudImage = false;
 

+ 22 - 19
src/PixiEditor.ChangeableDocument/Changes/Drawing/PathBasedPen_UpdateableChange.cs

@@ -1,20 +1,23 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal class PathBasedPen_UpdateableChange : UpdateableChange
 {
     private readonly Guid memberGuid;
-    private readonly SKColor color;
+    private readonly Color color;
     private readonly float strokeWidth;
     private readonly bool drawOnMask;
 
     private CommittedChunkStorage? storedChunks;
-    private SKPath tempPath = new();
+    private VectorPath tempPath = new();
 
     private List<VecD> points = new();
 
     [GenerateUpdateableChangeActions]
-    public PathBasedPen_UpdateableChange(Guid memberGuid, VecD pos, SKColor color, float strokeWidth, bool drawOnMask)
+    public PathBasedPen_UpdateableChange(Guid memberGuid, VecD pos, Color color, float strokeWidth, bool drawOnMask)
     {
         this.memberGuid = memberGuid;
         this.color = color;
@@ -34,7 +37,7 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         if (!DrawingChangeHelper.IsValidForDrawing(target, memberGuid, drawOnMask))
             return new Error();
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
-        image.SetBlendMode(SKBlendMode.SrcOver);
+        image.SetBlendMode(BlendMode.SrcOver);
         DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
         return new Success();
     }
@@ -52,7 +55,7 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         for (int i = 0; i < points.Count; i++)
         {
             UpdateTempPath(i + 1);
-            image.EnqueueDrawPath(tempPath, color, strokeWidth, SKStrokeCap.Round, SKBlendMode.Src);
+            image.EnqueueDrawPath(tempPath, color, strokeWidth, StrokeCap.Round, BlendMode.Src);
         }
     }
 
@@ -61,18 +64,18 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         tempPath.Reset();
         if (points.Count == 1)
         {
-            tempPath.MoveTo((SKPoint)points[0]);
+            tempPath.MoveTo((Point)points[0]);
             return;
         }
         if (points.Count == 2)
         {
-            tempPath.MoveTo((SKPoint)points[0]);
-            tempPath.LineTo((SKPoint)points[1]);
+            tempPath.MoveTo((Point)points[0]);
+            tempPath.LineTo((Point)points[1]);
             return;
         }
         var (mid, _) = FindCubicPoints(points[^3], points[^2], points[^1], points[^1]);
-        tempPath.MoveTo((SKPoint)points[^2]);
-        tempPath.QuadTo((SKPoint)mid, (SKPoint)points[^1]);
+        tempPath.MoveTo((Point)points[^2]);
+        tempPath.QuadTo((Point)mid, (Point)points[^1]);
     }
 
     private void UpdateTempPath(int pointsCount)
@@ -80,20 +83,20 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         tempPath.Reset();
         if (pointsCount is 1 or 2)
         {
-            tempPath.MoveTo((SKPoint)points[0]);
+            tempPath.MoveTo((Point)points[0]);
             return;
         }
         if (pointsCount == 3)
         {
             var (mid, _) = FindCubicPoints(points[0], points[1], points[2], points[2]);
-            tempPath.MoveTo((SKPoint)points[0]);
-            tempPath.QuadTo((SKPoint)mid, (SKPoint)points[2]);
+            tempPath.MoveTo((Point)points[0]);
+            tempPath.QuadTo((Point)mid, (Point)points[2]);
             return;
         }
 
         var (mid1, mid2) = FindCubicPoints(points[pointsCount - 4], points[pointsCount - 3], points[pointsCount - 2], points[pointsCount - 1]);
-        tempPath.MoveTo((SKPoint)points[pointsCount - 3]);
-        tempPath.CubicTo((SKPoint)mid1, (SKPoint)mid2, (SKPoint)points[pointsCount - 2]);
+        tempPath.MoveTo((Point)points[pointsCount - 3]);
+        tempPath.CubicTo((Point)mid1, (Point)mid2, (Point)points[pointsCount - 2]);
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
@@ -107,7 +110,7 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         {
             UpdateTempPathFinish();
 
-            image.EnqueueDrawPath(tempPath, color, strokeWidth, SKStrokeCap.Round, SKBlendMode.Src);
+            image.EnqueueDrawPath(tempPath, color, strokeWidth, StrokeCap.Round, BlendMode.Src);
             var affChunks = image.FindAffectedChunks();
             storedChunks = new CommittedChunkStorage(image, affChunks);
             image.CommitChanges();
@@ -116,7 +119,7 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         }
         else
         {
-            image.SetBlendMode(SKBlendMode.SrcOver);
+            image.SetBlendMode(BlendMode.SrcOver);
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
 
             FastforwardEnqueueDrawPath(image);
@@ -134,7 +137,7 @@ internal class PathBasedPen_UpdateableChange : UpdateableChange
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
 
         int opCount = image.QueueLength;
-        image.EnqueueDrawPath(tempPath, color, strokeWidth, SKStrokeCap.Round, SKBlendMode.Src);
+        image.EnqueueDrawPath(tempPath, color, strokeWidth, StrokeCap.Round, BlendMode.Src);
         var affChunks = image.FindAffectedChunks(opCount);
 
         return DrawingChangeHelper.CreateChunkChangeInfo(memberGuid, affChunks, drawOnMask);

+ 13 - 11
src/PixiEditor.ChangeableDocument/Changes/Drawing/PixelPerfectPen_UpdateableChange.cs

@@ -1,11 +1,13 @@
 using ChunkyImageLib.Operations;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 
 internal class PixelPerfectPen_UpdateableChange : UpdateableChange
 {
-    private readonly SKColor color;
+    private readonly Color color;
     private readonly bool drawOnMask;
     private readonly Guid memberGuid;
     private readonly HashSet<VecI> confirmedPixels = new();
@@ -15,7 +17,7 @@ internal class PixelPerfectPen_UpdateableChange : UpdateableChange
     private CommittedChunkStorage? chunkStorage;
 
     [GenerateUpdateableChangeActions]
-    public PixelPerfectPen_UpdateableChange(Guid memberGuid, VecI pos, SKColor color, bool drawOnMask)
+    public PixelPerfectPen_UpdateableChange(Guid memberGuid, VecI pos, Color color, bool drawOnMask)
     {
         this.memberGuid = memberGuid;
         this.color = color;
@@ -33,7 +35,7 @@ internal class PixelPerfectPen_UpdateableChange : UpdateableChange
         if (!DrawingChangeHelper.IsValidForDrawing(target, memberGuid, drawOnMask))
             return new Error();
         var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask);
-        image.SetBlendMode(SKBlendMode.SrcOver);
+        image.SetBlendMode(BlendMode.SrcOver);
         DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
         return new Success();
     }
@@ -52,7 +54,7 @@ internal class PixelPerfectPen_UpdateableChange : UpdateableChange
     {
         if (pointsCount == 1)
         {
-            image.EnqueueDrawPixel(incomingPoints![0], color, SKBlendMode.Src);
+            image.EnqueueDrawPixel(incomingPoints![0], color, BlendMode.Src);
             confirmedPixels.Add(incomingPoints[0]);
             return;
         }
@@ -68,17 +70,17 @@ internal class PixelPerfectPen_UpdateableChange : UpdateableChange
         (pixelsToConfirm2, pixelsToConfirm) = (pixelsToConfirm, pixelsToConfirm2);
         pixelsToConfirm.Clear();
 
-        SKPoint[] line = BresenhamLineHelper.GetBresenhamLine(incomingPoints[pointsCount - 2], incomingPoints[pointsCount - 1]);
-        foreach (VecI pixel in line)
+        Point[] line = BresenhamLineHelper.GetBresenhamLine(incomingPoints[pointsCount - 2], incomingPoints[pointsCount - 1]);
+        foreach (Point pixel in line)
         {
             pixelsToConfirm.Add(pixel);
         }
-        image.EnqueueDrawPixels(line.Select(point => (VecI)point), color, SKBlendMode.Src);
+        image.EnqueueDrawPixels(line.Select(point => new VecI((int)point.X, (int)point.Y)), color, BlendMode.Src);
 
         if (pointsCount >= 3 && IsLShape(pointsCount - 1) && !confirmedPixels.Contains(incomingPoints[pointsCount - 2]))
         {
             VecI pixelToErase = incomingPoints[pointsCount - 2];
-            image.EnqueueDrawPixel(pixelToErase, SKColors.Transparent, SKBlendMode.Src);
+            image.EnqueueDrawPixel(pixelToErase, Colors.Transparent, BlendMode.Src);
             pixelsToConfirm.Remove(pixelToErase);
             pixelsToConfirm2.Remove(pixelToErase);
             incomingPoints.RemoveAt(pointsCount - 2);
@@ -110,9 +112,9 @@ internal class PixelPerfectPen_UpdateableChange : UpdateableChange
         }
         else
         {
-            image.SetBlendMode(SKBlendMode.SrcOver);
+            image.SetBlendMode(BlendMode.SrcOver);
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
-            image.EnqueueDrawPixels(confirmedPixels, color, SKBlendMode.Src);
+            image.EnqueueDrawPixels(confirmedPixels, color, BlendMode.Src);
         }
 
         var affChunks = image.FindAffectedChunks();

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

@@ -1,15 +1,16 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal class ReplaceColor_Change : Change
 {
-    private readonly SKColor oldColor;
-    private readonly SKColor newColor;
+    private readonly Color oldColor;
+    private readonly Color newColor;
 
     private Dictionary<Guid, CommittedChunkStorage>? savedChunks;
 
     [GenerateMakeChangeAction]
-    public ReplaceColor_Change(SKColor oldColor, SKColor newColor)
+    public ReplaceColor_Change(Color oldColor, Color newColor)
     {
         this.oldColor = oldColor;
         this.newColor = newColor;

+ 3 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/ShiftLayer_UpdateableChange.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.ChangeableDocument.Changes.Drawing;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal class ShiftLayer_UpdateableChange : UpdateableChange
 {
     private readonly Guid layerGuid;

+ 16 - 13
src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelectedArea_UpdateableChange.cs

@@ -1,6 +1,9 @@
 using ChunkyImageLib.Operations;
 using PixiEditor.ChangeableDocument.Changes.Selection;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal class TransformSelectedArea_UpdateableChange : UpdateableChange
@@ -11,15 +14,15 @@ internal class TransformSelectedArea_UpdateableChange : UpdateableChange
     private ShapeCorners corners;
 
     private Dictionary<Guid, (Surface surface, VecI pos)>? images;
-    private SKMatrix globalMatrix;
+    private Matrix3X3 globalMatrix;
     private RectI originalTightBounds;
     private Dictionary<Guid, CommittedChunkStorage>? savedChunks;
 
-    private SKPath? originalPath;
+    private VectorPath? originalPath;
 
     private bool hasEnqueudImages = false;
 
-    private static SKPaint RegularPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
+    private static Paint RegularPaint { get; } = new () { BlendMode = BlendMode.SrcOver };
 
     [GenerateUpdateableChangeActions]
     public TransformSelectedArea_UpdateableChange(
@@ -45,7 +48,7 @@ internal class TransformSelectedArea_UpdateableChange : UpdateableChange
                 return new Error();
         }
 
-        originalPath = new SKPath(target.Selection.SelectionPath) { FillType = SKPathFillType.EvenOdd };
+        originalPath = new VectorPath(target.Selection.SelectionPath) { FillType = PathFillType.EvenOdd };
         RectI bounds = (RectI)originalPath.TightBounds;
 
         images = new();
@@ -64,7 +67,7 @@ internal class TransformSelectedArea_UpdateableChange : UpdateableChange
         return new Success();
     }
 
-    public OneOf<None, (Surface image, RectI extractedRect)> ExtractArea(ChunkyImage image, SKPath path, RectI pathBounds)
+    public OneOf<None, (Surface image, RectI extractedRect)> ExtractArea(ChunkyImage image, VectorPath path, RectI pathBounds)
     {
         // get rid of transparent areas on edges
         var memberImageBounds = image.FindLatestBounds();
@@ -76,15 +79,15 @@ internal class TransformSelectedArea_UpdateableChange : UpdateableChange
             return new None();
 
         // shift the clip to account for the image being smaller than the selection
-        SKPath clipPath = new SKPath(path) { FillType = SKPathFillType.EvenOdd };
-        clipPath.Transform(SKMatrix.CreateTranslation(-pathBounds.X, -pathBounds.Y));
+        VectorPath clipPath = new VectorPath(path) { FillType = PathFillType.EvenOdd };
+        clipPath.Transform(Matrix3X3.CreateTranslation(-pathBounds.X, -pathBounds.Y));
 
         // draw
         Surface output = new(pathBounds.Size);
-        output.SkiaSurface.Canvas.Save();
-        output.SkiaSurface.Canvas.ClipPath(clipPath);
-        image.DrawMostUpToDateRegionOn(pathBounds, ChunkResolution.Full, output.SkiaSurface, VecI.Zero);
-        output.SkiaSurface.Canvas.Restore();
+        output.DrawingSurface.Canvas.Save();
+        output.DrawingSurface.Canvas.ClipPath(clipPath);
+        image.DrawMostUpToDateRegionOn(pathBounds, ChunkResolution.Full, output.DrawingSurface, VecI.Zero);
+        output.DrawingSurface.Canvas.Restore();
 
         return (output, pathBounds);
     }
@@ -105,7 +108,7 @@ internal class TransformSelectedArea_UpdateableChange : UpdateableChange
 
         if (!keepOriginal)
             memberImage.EnqueueClearPath(originalPath!, originalTightBounds);
-        SKMatrix localMatrix = SKMatrix.CreateTranslation(originalPos.X - originalTightBounds.Left, originalPos.Y - originalTightBounds.Top);
+        Matrix3X3 localMatrix = Matrix3X3.CreateTranslation(originalPos.X - originalTightBounds.Left, originalPos.Y - originalTightBounds.Top);
         localMatrix = localMatrix.PostConcat(globalMatrix);
         memberImage.EnqueueDrawImage(localMatrix, image, RegularPaint, false);
         hasEnqueudImages = true;

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeCanvas_Change.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Root;
 

+ 17 - 14
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs

@@ -1,6 +1,9 @@
 using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.Enums;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using BlendMode = PixiEditor.DrawingApi.Core.Surface.BlendMode;
 
 namespace PixiEditor.ChangeableDocument.Changes.Root;
 
@@ -33,13 +36,13 @@ internal class ResizeImage_Change : Change
         return new Success();
     }
 
-    private static SKFilterQuality ToFilterQuality(ResamplingMethod method, bool downscaling) =>
+    private static FilterQuality ToFilterQuality(ResamplingMethod method, bool downscaling) =>
         (method, downscaling) switch
         {
-            (ResamplingMethod.NearestNeighbor, _) => SKFilterQuality.None,
-            (ResamplingMethod.Bilinear, true) => SKFilterQuality.Medium,
-            (ResamplingMethod.Bilinear, false) => SKFilterQuality.Low,
-            (ResamplingMethod.Bicubic, _) => SKFilterQuality.High,
+            (ResamplingMethod.NearestNeighbor, _) => FilterQuality.None,
+            (ResamplingMethod.Bilinear, true) => FilterQuality.Medium,
+            (ResamplingMethod.Bilinear, false) => FilterQuality.Low,
+            (ResamplingMethod.Bicubic, _) => FilterQuality.High,
             _ => throw new ArgumentOutOfRangeException(),
         };
 
@@ -49,22 +52,22 @@ internal class ResizeImage_Change : Change
         image.DrawMostUpToDateRegionOn(
             new(VecI.Zero, originalSize), 
             ChunkResolution.Full,
-            originalSurface.SkiaSurface,
+            originalSurface.DrawingSurface,
             VecI.Zero);
         
         bool downscaling = newSize.LengthSquared < originalSize.LengthSquared;
-        SKFilterQuality quality = ToFilterQuality(method, downscaling);
-        using SKPaint paint = new()
+        FilterQuality quality = ToFilterQuality(method, downscaling);
+        using Paint paint = new()
         {
             FilterQuality = quality, 
-            BlendMode = SKBlendMode.Src,
+            BlendMode = BlendMode.Src,
         };
 
         using Surface newSurface = new(newSize);
-        newSurface.SkiaSurface.Canvas.Save();
-        newSurface.SkiaSurface.Canvas.Scale(newSize.X / (float)originalSize.X, newSize.Y / (float)originalSize.Y);
-        newSurface.SkiaSurface.Canvas.DrawSurface(originalSurface.SkiaSurface, 0, 0, paint);
-        newSurface.SkiaSurface.Canvas.Restore();
+        newSurface.DrawingSurface.Canvas.Save();
+        newSurface.DrawingSurface.Canvas.Scale(newSize.X / (float)originalSize.X, newSize.Y / (float)originalSize.Y);
+        newSurface.DrawingSurface.Canvas.DrawSurface(originalSurface.DrawingSurface, 0, 0, paint);
+        newSurface.DrawingSurface.Canvas.Restore();
         
         image.EnqueueResize(newSize);
         image.EnqueueClear();

+ 7 - 7
src/PixiEditor.ChangeableDocument/Changes/Selection/ClearSelection_Change.cs

@@ -1,10 +1,10 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Selection;
 
 internal class ClearSelection_Change : Change
 {
-    private SKPath? originalPath;
+    private VectorPath? originalPath;
 
     [GenerateMakeChangeAction]
     public ClearSelection_Change() { }
@@ -13,25 +13,25 @@ internal class ClearSelection_Change : Change
     {
         if (target.Selection.SelectionPath.IsEmpty)
             return new Error();
-        originalPath = new SKPath(target.Selection.SelectionPath);
+        originalPath = new VectorPath(target.Selection.SelectionPath);
         return new Success();
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        (var toDispose, target.Selection.SelectionPath) = (target.Selection.SelectionPath, new SKPath());
+        (var toDispose, target.Selection.SelectionPath) = (target.Selection.SelectionPath, new VectorPath());
         toDispose.Dispose();
 
         ignoreInUndo = false;
-        return new Selection_ChangeInfo(new SKPath());
+        return new Selection_ChangeInfo(new VectorPath());
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        (var toDispose, target.Selection.SelectionPath) = (target.Selection.SelectionPath, new SKPath(originalPath));
+        (var toDispose, target.Selection.SelectionPath) = (target.Selection.SelectionPath, new VectorPath(originalPath!));
         toDispose.Dispose();
 
-        return new Selection_ChangeInfo(new SKPath(originalPath));
+        return new Selection_ChangeInfo(new VectorPath(originalPath!));
     }
 
     public override void Dispose()

+ 9 - 8
src/PixiEditor.ChangeableDocument/Changes/Selection/SelectEllipse_UpdateableChange.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Enums;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Selection;
 
@@ -7,7 +8,7 @@ internal class SelectEllipse_UpdateableChange : UpdateableChange
 {
     private RectI borders;
     private readonly SelectionMode mode;
-    private SKPath? originalPath;
+    private VectorPath? originalPath;
 
     [GenerateUpdateableChangeActions]
     public SelectEllipse_UpdateableChange(RectI borders, SelectionMode mode)
@@ -24,13 +25,13 @@ internal class SelectEllipse_UpdateableChange : UpdateableChange
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        originalPath = new SKPath(target.Selection.SelectionPath);
+        originalPath = new VectorPath(target.Selection.SelectionPath);
         return new Success();
     }
 
     private Selection_ChangeInfo CommonApply(Document target)
     {
-        using var ellipsePath = new SKPath() { FillType = SKPathFillType.EvenOdd };
+        using var ellipsePath = new VectorPath() { FillType = PathFillType.EvenOdd };
         if (!borders.IsZeroArea)
             ellipsePath.AddOval(borders);
 
@@ -38,10 +39,10 @@ internal class SelectEllipse_UpdateableChange : UpdateableChange
         if (mode == SelectionMode.New)
             target.Selection.SelectionPath = new(ellipsePath);
         else
-            target.Selection.SelectionPath = originalPath!.Op(ellipsePath, mode.ToSKPathOp());
+            target.Selection.SelectionPath = originalPath!.Op(ellipsePath, mode.ToVectorPathOp());
         toDispose.Dispose();
 
-        return new Selection_ChangeInfo(new SKPath(target.Selection.SelectionPath));
+        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
@@ -58,9 +59,9 @@ internal class SelectEllipse_UpdateableChange : UpdateableChange
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        (var toDispose, target.Selection.SelectionPath) = (target.Selection.SelectionPath, new SKPath(originalPath));
+        (var toDispose, target.Selection.SelectionPath) = (target.Selection.SelectionPath, new VectorPath(originalPath!));
         toDispose.Dispose();
-        return new Selection_ChangeInfo(new SKPath(target.Selection.SelectionPath));
+        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
     }
 
     public override void Dispose()

+ 10 - 9
src/PixiEditor.ChangeableDocument/Changes/Selection/SelectLasso_UpdateableChange.cs

@@ -1,11 +1,12 @@
 using PixiEditor.ChangeableDocument.Enums;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Selection;
 internal class SelectLasso_UpdateableChange : UpdateableChange
 {
-    private SKPath? originalPath;
-    private SKPath path = new() { FillType = SKPathFillType.EvenOdd };
+    private VectorPath? originalPath;
+    private VectorPath path = new() { FillType = PathFillType.EvenOdd };
     private readonly SelectionMode mode;
 
     [GenerateUpdateableChangeActions]
@@ -23,7 +24,7 @@ internal class SelectLasso_UpdateableChange : UpdateableChange
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        originalPath = new SKPath(target.Selection.SelectionPath);
+        originalPath = new VectorPath(target.Selection.SelectionPath);
         return new Success();
     }
 
@@ -32,17 +33,17 @@ internal class SelectLasso_UpdateableChange : UpdateableChange
         var toDispose = target.Selection.SelectionPath;
         if (mode == SelectionMode.New)
         {
-            var copy = new SKPath(path);
+            var copy = new VectorPath(path);
             copy.Close();
             target.Selection.SelectionPath = copy;
         }
         else
         {
-            target.Selection.SelectionPath = originalPath!.Op(path, mode.ToSKPathOp());
+            target.Selection.SelectionPath = originalPath!.Op(path, mode.ToVectorPathOp());
         }
         toDispose.Dispose();
 
-        return new Selection_ChangeInfo(new SKPath(target.Selection.SelectionPath));
+        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
     }
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
@@ -58,9 +59,9 @@ internal class SelectLasso_UpdateableChange : UpdateableChange
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
         var toDispose = target.Selection.SelectionPath;
-        target.Selection.SelectionPath = new(originalPath);
+        target.Selection.SelectionPath = new(originalPath!);
         toDispose.Dispose();
-        return new Selection_ChangeInfo(new SKPath(target.Selection.SelectionPath));
+        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
     }
 
     public override void Dispose()

+ 9 - 8
src/PixiEditor.ChangeableDocument/Changes/Selection/SelectRectangle_UpdateableChange.cs

@@ -1,11 +1,12 @@
 using PixiEditor.ChangeableDocument.Enums;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Selection;
 
 internal class SelectRectangle_UpdateableChange : UpdateableChange
 {
-    private SKPath? originalPath;
+    private VectorPath? originalPath;
     private RectI rect;
     private readonly SelectionMode mode;
 
@@ -17,7 +18,7 @@ internal class SelectRectangle_UpdateableChange : UpdateableChange
     }
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
     {
-        originalPath = new SKPath(target.Selection.SelectionPath);
+        originalPath = new VectorPath(target.Selection.SelectionPath);
         return new Success();
     }
 
@@ -29,7 +30,7 @@ internal class SelectRectangle_UpdateableChange : UpdateableChange
 
     private Selection_ChangeInfo CommonApply(Document target)
     {
-        using var rectPath = new SKPath() { FillType = SKPathFillType.EvenOdd };
+        using var rectPath = new VectorPath() { FillType = PathFillType.EvenOdd };
         if (!rect.IsZeroArea)
         {
             rectPath.MoveTo(rect.TopLeft);
@@ -43,10 +44,10 @@ internal class SelectRectangle_UpdateableChange : UpdateableChange
         if (mode == SelectionMode.New)
             target.Selection.SelectionPath = new(rectPath);
         else
-            target.Selection.SelectionPath = originalPath!.Op(rectPath, mode.ToSKPathOp());
+            target.Selection.SelectionPath = originalPath!.Op(rectPath, mode.ToVectorPathOp());
         toDispose.Dispose();
 
-        return new Selection_ChangeInfo(new SKPath(target.Selection.SelectionPath));
+        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
@@ -63,9 +64,9 @@ internal class SelectRectangle_UpdateableChange : UpdateableChange
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        (var toDispose, target.Selection.SelectionPath) = (target.Selection.SelectionPath, new SKPath(originalPath));
+        (var toDispose, target.Selection.SelectionPath) = (target.Selection.SelectionPath, new VectorPath(originalPath!));
         toDispose.Dispose();
-        return new Selection_ChangeInfo(new SKPath(target.Selection.SelectionPath));
+        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
     }
 
     public override void Dispose()

+ 6 - 5
src/PixiEditor.ChangeableDocument/Changes/Selection/SelectionChangeHelper.cs

@@ -1,15 +1,16 @@
 using ChunkyImageLib.Operations;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Selection;
 internal class SelectionChangeHelper
 {
     public static Selection_ChangeInfo DoSelectionTransform(
-        Document target, SKPath originalPath, RectI originalPathTightBounds, ShapeCorners to)
+        Document target, VectorPath originalPath, RectI originalPathTightBounds, ShapeCorners to)
     {
-        SKPath newPath = new(originalPath);
+        VectorPath newPath = new(originalPath);
 
-        var matrix = SKMatrix.CreateTranslation((float)-originalPathTightBounds.X, (float)-originalPathTightBounds.Y).PostConcat(
+        var matrix = Matrix3X3.CreateTranslation((float)-originalPathTightBounds.X, (float)-originalPathTightBounds.Y).PostConcat(
             OperationHelper.CreateMatrixFromPoints(to, originalPathTightBounds.Size));
         newPath.Transform(matrix);
 
@@ -17,6 +18,6 @@ internal class SelectionChangeHelper
         target.Selection.SelectionPath = newPath;
         toDispose.Dispose();
 
-        return new Selection_ChangeInfo(new SKPath(target.Selection.SelectionPath));
+        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
     }
 }

+ 9 - 9
src/PixiEditor.ChangeableDocument/Changes/Selection/SetSelection_Change.cs

@@ -1,16 +1,16 @@
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Selection;
 internal class SetSelection_Change : Change
 {
-    private readonly SKPath selection;
-    private SKPath? originalSelection;
+    private readonly VectorPath selection;
+    private VectorPath? originalSelection;
 
     [GenerateMakeChangeAction]
-    public SetSelection_Change(SKPath selection)
+    public SetSelection_Change(VectorPath selection)
     {
-        this.selection = new SKPath(selection) { FillType = SKPathFillType.EvenOdd };
+        this.selection = new VectorPath(selection) { FillType = PathFillType.EvenOdd };
     }
 
     public override OneOf<Success, Error> InitializeAndValidate(Document target)
@@ -21,14 +21,14 @@ internal class SetSelection_Change : Change
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
-        target.Selection.SelectionPath = new SKPath(selection) { FillType = SKPathFillType.EvenOdd };
+        target.Selection.SelectionPath = new VectorPath(selection) { FillType = PathFillType.EvenOdd };
         ignoreInUndo = false;
-        return new Selection_ChangeInfo(new SKPath(selection) { FillType = SKPathFillType.EvenOdd });
+        return new Selection_ChangeInfo(new VectorPath(selection) { FillType = PathFillType.EvenOdd });
     }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        target.Selection.SelectionPath = new SKPath(originalSelection) { FillType = SKPathFillType.EvenOdd };
-        return new Selection_ChangeInfo(new SKPath(originalSelection) { FillType = SKPathFillType.EvenOdd });
+        target.Selection.SelectionPath = new VectorPath(originalSelection!) { FillType = PathFillType.EvenOdd };
+        return new Selection_ChangeInfo(new VectorPath(originalSelection!) { FillType = PathFillType.EvenOdd });
     }
 }

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changes/Selection/TransformSelectionPath_UpdateableChange.cs

@@ -1,9 +1,10 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Changes.Selection;
 internal class TransformSelectionPath_UpdateableChange : UpdateableChange
 {
-    private SKPath? originalPath;
+    private VectorPath? originalPath;
     private RectI originalTightBounds;
     private ShapeCorners newCorners;
 
@@ -42,9 +43,9 @@ internal class TransformSelectionPath_UpdateableChange : UpdateableChange
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
         var toDispose = target.Selection.SelectionPath;
-        target.Selection.SelectionPath = new SKPath(originalPath);
+        target.Selection.SelectionPath = new VectorPath(originalPath!);
         toDispose.Dispose();
-        return new Selection_ChangeInfo(new SKPath(target.Selection.SelectionPath));
+        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
     }
 
     public override void Dispose()

+ 1 - 6
src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateLayer_Change.cs

@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
+using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 
 namespace PixiEditor.ChangeableDocument.Changes.Structure;
 internal class DuplicateLayer_Change : Change

+ 3 - 1
src/PixiEditor.ChangeableDocument/Enums/ResizeAnchorEx.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Enums;
 
 internal static class ResizeAnchorEx
 {

+ 5 - 5
src/PixiEditor.ChangeableDocument/Enums/SelectionModeEx.cs

@@ -1,16 +1,16 @@
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.ChangeableDocument.Enums;
 internal static class SelectionModeEx
 {
-    public static SKPathOp ToSKPathOp(this SelectionMode mode)
+    public static VectorPathOp ToVectorPathOp(this SelectionMode mode)
     {
         return mode switch
         {
             SelectionMode.New => throw new ArgumentException("The New mode has no corresponding operation"),
-            SelectionMode.Add => SKPathOp.Union,
-            SelectionMode.Subtract => SKPathOp.Difference,
-            SelectionMode.Intersect => SKPathOp.Intersect,
+            SelectionMode.Add => VectorPathOp.Union,
+            SelectionMode.Subtract => VectorPathOp.Difference,
+            SelectionMode.Intersect => VectorPathOp.Intersect,
             _ => throw new NotImplementedException(),
         };
     }

+ 17 - 15
src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs

@@ -1,12 +1,14 @@
 using ChunkyImageLib.Operations;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace PixiEditor.ChangeableDocument.Rendering;
 
 public static class ChunkRenderer
 {
-    private static readonly SKPaint ClippingPaint = new SKPaint() { BlendMode = SKBlendMode.DstIn };
+    private static readonly Paint ClippingPaint = new Paint() { BlendMode = BlendMode.DstIn };
 
     public static OneOf<Chunk, EmptyChunk> MergeWholeStructure(VecI chunkPos, ChunkResolution resolution, IReadOnlyFolder root)
     {
@@ -48,13 +50,13 @@ public static class ChunkRenderer
         context.UpdateFromMember(layer);
 
         Chunk renderingResult = Chunk.Create(resolution);
-        if (!layer.LayerImage.DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.SkiaSurface, VecI.Zero, context.ReplacingPaintWithOpacity))
+        if (!layer.LayerImage.DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.DrawingSurface, VecI.Zero, context.ReplacingPaintWithOpacity))
         {
             renderingResult.Dispose();
             return new EmptyChunk();
         }
 
-        if (!layer.Mask!.DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.SkiaSurface, VecI.Zero, ClippingPaint))
+        if (!layer.Mask!.DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.DrawingSurface, VecI.Zero, ClippingPaint))
         {
             // should pretty much never happen due to the check above, but you can never be sure with many threads
             renderingResult.Dispose();
@@ -62,9 +64,9 @@ public static class ChunkRenderer
         }
 
         if (clippingChunk.IsT2)
-            OperationHelper.ClampAlpha(renderingResult.Surface.SkiaSurface, clippingChunk.AsT2.Surface.SkiaSurface);
+            OperationHelper.ClampAlpha(renderingResult.Surface.DrawingSurface, clippingChunk.AsT2.Surface.DrawingSurface);
 
-        targetChunk.Surface.SkiaSurface.Canvas.DrawSurface(renderingResult.Surface.SkiaSurface, 0, 0, context.BlendModePaint);
+        targetChunk.Surface.DrawingSurface.Canvas.DrawSurface(renderingResult.Surface.DrawingSurface, 0, 0, context.BlendModePaint);
         return renderingResult;
     }
 
@@ -79,15 +81,15 @@ public static class ChunkRenderer
 
         context.UpdateFromMember(layer);
         Chunk renderingResult = Chunk.Create(resolution);
-        if (!layer.LayerImage.DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.SkiaSurface, VecI.Zero, context.ReplacingPaintWithOpacity))
+        if (!layer.LayerImage.DrawMostUpToDateChunkOn(chunkPos, resolution, renderingResult.Surface.DrawingSurface, VecI.Zero, context.ReplacingPaintWithOpacity))
         {
             renderingResult.Dispose();
             return new EmptyChunk();
         }
 
         if (clippingChunk.IsT2)
-            OperationHelper.ClampAlpha(renderingResult.Surface.SkiaSurface, clippingChunk.AsT2.Surface.SkiaSurface);
-        targetChunk.Surface.SkiaSurface.Canvas.DrawSurface(renderingResult.Surface.SkiaSurface, 0, 0, context.BlendModePaint);
+            OperationHelper.ClampAlpha(renderingResult.Surface.DrawingSurface, clippingChunk.AsT2.Surface.DrawingSurface);
+        targetChunk.Surface.DrawingSurface.Canvas.DrawSurface(renderingResult.Surface.DrawingSurface, 0, 0, context.BlendModePaint);
         return renderingResult;
     }
 
@@ -112,7 +114,7 @@ public static class ChunkRenderer
             return;
         }
         context.UpdateFromMember(layer);
-        layer.LayerImage.DrawMostUpToDateChunkOn(chunkPos, resolution, targetChunk.Surface.SkiaSurface, VecI.Zero, context.BlendModeOpacityPaint);
+        layer.LayerImage.DrawMostUpToDateChunkOn(chunkPos, resolution, targetChunk.Surface.DrawingSurface, VecI.Zero, context.BlendModeOpacityPaint);
     }
 
     private static OneOf<EmptyChunk, Chunk> RenderFolder(
@@ -140,7 +142,7 @@ public static class ChunkRenderer
 
         if (folder.Mask is not null && folder.MaskIsVisible)
         {
-            if (!folder.Mask.DrawMostUpToDateChunkOn(chunkPos, resolution, contents.Surface.SkiaSurface, VecI.Zero, ClippingPaint))
+            if (!folder.Mask.DrawMostUpToDateChunkOn(chunkPos, resolution, contents.Surface.DrawingSurface, VecI.Zero, ClippingPaint))
             {
                 // this shouldn't really happen due to the check above, but another thread could edit the mask in the meantime
                 contents.Dispose();
@@ -149,10 +151,10 @@ public static class ChunkRenderer
         }
 
         if (clippingChunk.IsT2)
-            OperationHelper.ClampAlpha(contents.Surface.SkiaSurface, clippingChunk.AsT2.Surface.SkiaSurface);
+            OperationHelper.ClampAlpha(contents.Surface.DrawingSurface, clippingChunk.AsT2.Surface.DrawingSurface);
         context.UpdateFromMember(folder);
-        contents.Surface.SkiaSurface.Canvas.DrawSurface(contents.Surface.SkiaSurface, 0, 0, context.ReplacingPaintWithOpacity);
-        targetChunk.Surface.SkiaSurface.Canvas.DrawSurface(contents.Surface.SkiaSurface, 0, 0, context.BlendModePaint);
+        contents.Surface.DrawingSurface.Canvas.DrawSurface(contents.Surface.DrawingSurface, 0, 0, context.ReplacingPaintWithOpacity);
+        targetChunk.Surface.DrawingSurface.Canvas.DrawSurface(contents.Surface.DrawingSurface, 0, 0, context.BlendModePaint);
 
         return contents;
     }
@@ -168,7 +170,7 @@ public static class ChunkRenderer
             return new EmptyChunk();
 
         Chunk targetChunk = Chunk.Create(resolution);
-        targetChunk.Surface.SkiaSurface.Canvas.Clear();
+        targetChunk.Surface.DrawingSurface.Canvas.Clear();
 
         OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk = new FilledChunk();
         for (int i = 0; i < folder.Children.Count; i++)

+ 29 - 26
src/PixiEditor.ChangeableDocument/Rendering/RenderingContext.cs

@@ -1,18 +1,21 @@
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
-using PixiEditor.ChangeableDocument.Enums;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
+using DrawingApiBlendMode = PixiEditor.DrawingApi.Core.Surface.BlendMode;
 
 namespace PixiEditor.ChangeableDocument.Rendering;
 internal class RenderingContext : IDisposable
 {
-    public SKPaint BlendModePaint = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
-    public SKPaint BlendModeOpacityPaint = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
-    public SKPaint ReplacingPaintWithOpacity = new SKPaint() { BlendMode = SKBlendMode.Src };
+    public Paint BlendModePaint = new () { BlendMode = DrawingApiBlendMode.SrcOver };
+    public Paint BlendModeOpacityPaint = new () { BlendMode = DrawingApiBlendMode.SrcOver };
+    public Paint ReplacingPaintWithOpacity = new () { BlendMode = DrawingApiBlendMode.Src };
 
     public void UpdateFromMember(IReadOnlyStructureMember member)
     {
-        SKColor opacityColor = new(255, 255, 255, (byte)Math.Round(member.Opacity * 255));
-        SKBlendMode blendMode = GetSKBlendMode(member.BlendMode);
+        Color opacityColor = new(255, 255, 255, (byte)Math.Round(member.Opacity * 255));
+        DrawingApiBlendMode blendMode = GetDrawingBlendMode(member.BlendMode);
 
         BlendModeOpacityPaint.Color = opacityColor;
         BlendModeOpacityPaint.BlendMode = blendMode;
@@ -20,28 +23,28 @@ internal class RenderingContext : IDisposable
         ReplacingPaintWithOpacity.Color = opacityColor;
     }
 
-    private static SKBlendMode GetSKBlendMode(BlendMode blendMode)
+    private static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
     {
         return blendMode switch
         {
-            BlendMode.Normal => SKBlendMode.SrcOver,
-            BlendMode.Darken => SKBlendMode.Darken,
-            BlendMode.Multiply => SKBlendMode.Multiply,
-            BlendMode.ColorBurn => SKBlendMode.ColorBurn,
-            BlendMode.Lighten => SKBlendMode.Lighten,
-            BlendMode.Screen => SKBlendMode.Screen,
-            BlendMode.ColorDodge => SKBlendMode.ColorDodge,
-            BlendMode.LinearDodge => SKBlendMode.Plus,
-            BlendMode.Overlay => SKBlendMode.Overlay,
-            BlendMode.SoftLight => SKBlendMode.SoftLight,
-            BlendMode.HardLight => SKBlendMode.HardLight,
-            BlendMode.Difference => SKBlendMode.Difference,
-            BlendMode.Exclusion => SKBlendMode.Exclusion,
-            BlendMode.Hue => SKBlendMode.Hue,
-            BlendMode.Saturation => SKBlendMode.Saturation,
-            BlendMode.Luminosity => SKBlendMode.Luminosity,
-            BlendMode.Color => SKBlendMode.Color,
-            _ => SKBlendMode.SrcOver,
+            BlendMode.Normal => DrawingApiBlendMode.SrcOver,
+            BlendMode.Darken => DrawingApiBlendMode.Darken,
+            BlendMode.Multiply => DrawingApiBlendMode.Multiply,
+            BlendMode.ColorBurn => DrawingApiBlendMode.ColorBurn,
+            BlendMode.Lighten => DrawingApiBlendMode.Lighten,
+            BlendMode.Screen => DrawingApiBlendMode.Screen,
+            BlendMode.ColorDodge => DrawingApiBlendMode.ColorDodge,
+            BlendMode.LinearDodge => DrawingApiBlendMode.Plus,
+            BlendMode.Overlay => DrawingApiBlendMode.Overlay,
+            BlendMode.SoftLight => DrawingApiBlendMode.SoftLight,
+            BlendMode.HardLight => DrawingApiBlendMode.HardLight,
+            BlendMode.Difference => DrawingApiBlendMode.Difference,
+            BlendMode.Exclusion => DrawingApiBlendMode.Exclusion,
+            BlendMode.Hue => DrawingApiBlendMode.Hue,
+            BlendMode.Saturation => DrawingApiBlendMode.Saturation,
+            BlendMode.Luminosity => DrawingApiBlendMode.Luminosity,
+            BlendMode.Color => DrawingApiBlendMode.Color,
+            _ => DrawingApiBlendMode.SrcOver,
         };
     }
 

+ 32 - 0
src/PixiEditor.DrawingApi.Core/Bridge/DrawingBackendApi.cs

@@ -0,0 +1,32 @@
+using System;
+using PixiEditor.DrawingApi.Core.Exceptions;
+
+namespace PixiEditor.DrawingApi.Core.Bridge
+{
+    public static class DrawingBackendApi
+    {
+        private static IDrawingBackend _current;
+
+        public static IDrawingBackend Current
+        {
+            get
+            {
+                if (_current == null)
+                    throw new NullReferenceException("Either drawing backend was not yet initialized or reference was somehow lost.");
+
+                return _current;
+            }
+        }
+        
+        public static void SetupBackend(IDrawingBackend backend)
+        {
+            if (_current != null)
+            {
+                throw new InitializationDuplicateException("Drawing backend was already initialized.");
+            }
+            
+            _current = backend;
+            backend.Setup();
+        }
+    }
+}

+ 21 - 0
src/PixiEditor.DrawingApi.Core/Bridge/IDrawingBackend.cs

@@ -0,0 +1,21 @@
+using PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+using PixiEditor.DrawingApi.Core.Bridge.Operations;
+
+namespace PixiEditor.DrawingApi.Core.Bridge
+{
+    public interface IDrawingBackend
+    {
+        public void Setup();
+        public IColorImplementation ColorImplementation { get; }
+        public IImageImplementation ImageImplementation { get; }
+        public ICanvasImplementation CanvasImplementation { get; }
+        public IPaintImplementation PaintImplementation { get; }
+        public IVectorPathImplementation PathImplementation { get; }
+        public IMatrix3X3Implementation MatrixImplementation { get; }
+        public IPixmapImplementation PixmapImplementation { get; }
+        public ISurfaceImplementation SurfaceImplementation { get; }
+        public IColorSpaceImplementation ColorSpaceImplementation { get; }
+        public IImgDataImplementation ImgDataImplementation { get; }
+        public IBitmapImplementation BitmapImplementation { get; }
+    }
+}

+ 10 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IBitmapImplementation.cs

@@ -0,0 +1,10 @@
+using System;
+using PixiEditor.DrawingApi.Core.Surface;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+
+public interface IBitmapImplementation
+{
+    public void Dispose(IntPtr objectPointer);
+    public Bitmap Decode(ReadOnlySpan<byte> buffer);
+}

+ 12 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IColorImplementation.cs

@@ -0,0 +1,12 @@
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl
+{
+    public interface IColorImplementation
+    {
+        public ColorF ColorToColorF(uint colorValue);
+        public Color ColorFToColor(ColorF color);
+        public ColorType GetPlatformColorType();
+    }
+}

+ 10 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IColorSpaceImplementation.cs

@@ -0,0 +1,10 @@
+using System;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+
+public interface IColorSpaceImplementation
+{
+    public ColorSpace CreateSrgb();
+    public void Dispose(IntPtr objectPointer);
+}

+ 12 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IImgDataImplementation.cs

@@ -0,0 +1,12 @@
+using System;
+using System.IO;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+
+public interface IImgDataImplementation
+{
+    public void Dispose(IntPtr objectPointer);
+    public void SaveTo(ImgData imgData, FileStream stream);
+    public Stream AsStream(ImgData imgData);
+}

+ 11 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IMatrix3x3Implementation.cs

@@ -0,0 +1,11 @@
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+
+public interface IMatrix3X3Implementation
+{
+    public bool TryInvert(Matrix3X3 matrix, out Matrix3X3 inversedResult);
+    public Matrix3X3 Concat(in Matrix3X3 first, in Matrix3X3 second);
+    public Matrix3X3 PostConcat(in Matrix3X3 first, in Matrix3X3 second);
+    public VecD MapPoint(Matrix3X3 matrix, int p0, int p1);
+}

+ 28 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs

@@ -0,0 +1,28 @@
+using System;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl
+{
+    public interface IPaintImplementation
+    {
+        public IntPtr CreatePaint();
+        public void Dispose(IntPtr paintObjPointer);
+        public Paint Clone(IntPtr paintObjPointer);
+        public Color GetColor(Paint paint);
+        public void SetColor(Paint paint, Color value);
+        public BlendMode GetBlendMode(Paint paint);
+        public void SetBlendMode(Paint paint, BlendMode value);
+        public FilterQuality GetFilterQuality(Paint paint);
+        public void SetFilterQuality(Paint paint, FilterQuality value);
+        public bool GetIsAntiAliased(Paint paint);
+        public void SetIsAntiAliased(Paint paint, bool value);
+        public PaintStyle GetStyle(Paint paint);
+        public void SetStyle(Paint paint, PaintStyle value);
+        public StrokeCap GetStrokeCap(Paint paint);
+        public void SetStrokeCap(Paint paint, StrokeCap value);
+        public float GetStrokeWidth(Paint paint);
+        public void SetStrokeWidth(Paint paint, float value);
+    }
+}

+ 15 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPixmapImplementation.cs

@@ -0,0 +1,15 @@
+using System;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+
+public interface IPixmapImplementation
+{
+    public void Dispose(IntPtr objectPointer);
+    public IntPtr GetPixels(IntPtr objectPointer);
+    public Span<T> GetPixelSpan<T>(Pixmap pixmap) where T : unmanaged;
+    public IntPtr Construct(IntPtr dataPtr, ImageInfo imgInfo);
+    public int GetWidth(Pixmap pixmap);
+    public int GetHeight(Pixmap pixmap);
+}

+ 37 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IVectorPathImplementation.cs

@@ -0,0 +1,37 @@
+using System;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl;
+
+public interface IVectorPathImplementation
+{
+    public PathFillType GetFillType(VectorPath path);
+    public void SetFillType(VectorPath path, PathFillType fillType);
+    public PathConvexity GetConvexity(VectorPath path);
+    public void SetConvexity(VectorPath path, PathConvexity convexity);
+    public void Dispose(VectorPath path);
+    public bool IsPathOval(VectorPath path);
+    public bool IsRoundRect(VectorPath path);
+    public bool IsLine(VectorPath path);
+    public bool IsRect(VectorPath path);
+    public PathSegmentMask GetSegmentMasks(VectorPath path);
+    public int GetVerbCount(VectorPath path);
+    public int GetPointCount(VectorPath path);
+    public IntPtr Create();
+    public IntPtr Clone(VectorPath other);
+    public RectD GetTightBounds(VectorPath vectorPath);
+    public void Transform(VectorPath vectorPath, Matrix3X3 matrix);
+    public RectD GetBounds(VectorPath vectorPath);
+    public void Reset(VectorPath vectorPath);
+    public void MoveTo(VectorPath vectorPath, Point point);
+    public void LineTo(VectorPath vectorPath, Point point);
+    public void QuadTo(VectorPath vectorPath, Point mid, Point point);
+    public void CubicTo(VectorPath vectorPath, Point mid1, Point mid2, Point point);
+    public void ArcTo(VectorPath vectorPath, RectI oval, int startAngle, int sweepAngle, bool forceMoveTo);
+    public void AddOval(VectorPath vectorPath, RectI borders);
+    public VectorPath Op(VectorPath vectorPath, VectorPath ellipsePath, VectorPathOp pathOp);
+    public void Close(VectorPath vectorPath);
+    public string ToSvgPathData(VectorPath vectorPath);
+}

+ 38 - 0
src/PixiEditor.DrawingApi.Core/Bridge/Operations/ICanvasImplementation.cs

@@ -0,0 +1,38 @@
+using System;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.Operations
+{
+    public interface ICanvasImplementation
+    {
+        public void DrawPixel(IntPtr objPtr, int posX, int posY, Paint drawingPaint);
+        public void DrawSurface(IntPtr objPtr, DrawingSurface drawingSurface, int x, int y, Paint? paint);
+        public void DrawImage(IntPtr objPtr, Image image, int x, int y);
+        public int Save(IntPtr objPtr);
+        public void Restore(IntPtr objPtr);
+        public void Scale(IntPtr objPtr, float sizeX, float sizeY);
+        public void Translate(IntPtr objPtr, float translationX, float translationY);
+        public void DrawPath(IntPtr objPtr, VectorPath path, Paint paint);
+        public void DrawPoint(IntPtr objPtr, VecI pos, Paint paint);
+        public void DrawPoints(IntPtr objPtr, PointMode pointMode, Point[] points, Paint paint);
+        public void DrawRect(IntPtr objPtr, int x, int y, int width, int height, Paint paint);
+        public void ClipPath(IntPtr objPtr, VectorPath clipPath, ClipOperation clipOperation, bool antialias);
+        public void ClipRect(IntPtr objPtr, RectD rect, ClipOperation clipOperation);
+        public void Clear(IntPtr objPtr);
+        public void Clear(IntPtr objPtr, Color color);
+        public void DrawLine(IntPtr objPtr, VecI from, VecI to, Paint paint);
+        public void Flush(IntPtr objPtr);
+        public void SetMatrix(IntPtr objPtr, Matrix3X3 finalMatrix);
+        public void RestoreToCount(IntPtr objPtr, int count);
+        public void DrawColor(IntPtr objPtr, Color color, BlendMode paintBlendMode);
+        public void RotateRadians(IntPtr objPtr, float radians, float centerX, float centerY);
+        public void DrawImage(IntPtr objPtr, Image image, RectD rect, Paint paint);
+        public void DrawBitmap(IntPtr objPtr, Bitmap bitmap, int x, int y);
+        public void Dispose(IntPtr objectPointer);
+    }
+}

+ 17 - 0
src/PixiEditor.DrawingApi.Core/Bridge/Operations/IImageImplementation.cs

@@ -0,0 +1,17 @@
+using System;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.Operations
+{
+    public interface IImageImplementation
+    {
+        public Image Snapshot(DrawingSurface drawingSurface);
+        public void DisposeImage(Image image);
+        public Image FromEncodedData(string path);
+        public void GetColorShifts(ref int platformColorAlphaShift, ref int platformColorRedShift, ref int platformColorGreenShift, ref int platformColorBlueShift);
+        public ImgData Encode(Image image);
+        public int GetWidth(IntPtr objectPointer);
+        public int GetHeight(IntPtr objectPointer);
+    }
+}

+ 18 - 0
src/PixiEditor.DrawingApi.Core/Bridge/Operations/ISurfaceImplementation.cs

@@ -0,0 +1,18 @@
+using System;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.Operations;
+
+public interface ISurfaceImplementation
+{
+    public Pixmap PeekPixels(DrawingSurface drawingSurface);
+    public DrawingSurface Create(ImageInfo imageInfo, IntPtr pixels, int rowBytes);
+    public bool ReadPixels(DrawingSurface drawingSurface, ImageInfo dstInfo, IntPtr dstPixels, int dstRowBytes, int srcX, int srcY);
+    public void Draw(DrawingSurface drawingSurface, Canvas surfaceToDraw, int x, int y, Paint drawingPaint);
+    public DrawingSurface Create(ImageInfo imageInfo, IntPtr pixelBuffer);
+    public DrawingSurface Create(Pixmap pixmap);
+    public DrawingSurface Create(ImageInfo imageInfo);
+    public void Dispose(DrawingSurface drawingSurface);
+}

+ 212 - 0
src/PixiEditor.DrawingApi.Core/ColorsImpl/Color.cs

@@ -0,0 +1,212 @@
+using System;
+using System.Globalization;
+
+namespace PixiEditor.DrawingApi.Core.ColorsImpl
+{
+   /// <summary>32-bit ARGB unpremultiplied color value.</summary>
+  /// <remarks>The color components are always in a known order.</remarks>
+  public readonly struct Color : IEquatable<Color>
+  {
+    /// <summary>Gets an "empty" color, with zero for all the components.</summary>
+    public static readonly Color Empty = default;
+    private readonly uint _colorValue;
+
+    public Color(uint value) => this._colorValue = value;
+    public Color(byte red, byte green, byte blue, byte alpha) => this._colorValue = (uint) ((int) alpha << 24 | (int) red << 16 | (int) green << 8) | (uint) blue;
+
+    public Color(byte red, byte green, byte blue) => this._colorValue = (uint) (-16777216 | (int) red << 16 | (int) green << 8) | (uint) blue;
+
+    /// <param name="red">The new red component.</param>
+    /// <summary>Returns a new color based on this current instance, but with the new red channel value.</summary>
+    public Color WithRed(byte red) => new Color(red, this.G, this.B, this.A);
+
+    /// <param name="green">The new green component.</param>
+    /// <summary>Returns a new color based on this current instance, but with the new green channel value.</summary>
+    public Color WithGreen(byte green) => new Color(this.R, green, this.B, this.A);
+
+    /// <param name="blue">The new blue component.</param>
+    /// <summary>Returns a new color based on this current instance, but with the new blue channel value.</summary>
+    public Color WithBlue(byte blue) => new Color(this.R, this.G, blue, this.A);
+
+    /// <param name="alpha">The new alpha component.</param>
+    /// <summary>Returns a new color based on this current instance, but with the new alpha channel value.</summary>
+    public Color WithAlpha(byte alpha) => new Color(this.R, this.G, this.B, alpha);
+
+    /// <summary>Gets the alpha component of the color.</summary>
+    /// <value />
+    public byte A => (byte)(this._colorValue >> 24 & (uint) byte.MaxValue);
+
+    /// <summary>Gets the red component of the color.</summary>
+    /// <value />
+    public byte R => (byte)(this._colorValue >> 16 & (uint) byte.MaxValue);
+
+    /// <summary>Gets the green component of the color.</summary>
+    /// <value />
+    public byte G => (byte)(this._colorValue >> 8 & (uint) byte.MaxValue);
+
+    /// <summary>Gets the blue component of the color.</summary>
+    /// <value />
+    public byte B => (byte)(this._colorValue & (uint) byte.MaxValue);
+
+    /// <summary>Gets the hue value.</summary>
+    /// <value />
+    public float Hue
+    {
+      get
+      {
+        float h;
+        this.ToHsv(out h, out float _, out float _);
+        return h;
+      }
+    }
+
+    /// <param name="h">The hue value.</param>
+    /// <param name="s">The saturation value.</param>
+    /// <param name="l">The lightness/luminosity value.</param>
+    /// <param name="a">The alpha value.</param>
+    /// <summary>Creates a color from the specified hue, saturation, lightness/luminosity and alpha values.</summary>
+    /// <returns>The new <see cref="T:SkiaSharp.Color" /> instance.</returns>
+    public static Color FromHsl(float h, float s, float l, byte a = 255)
+    {
+      ColorF ColorF = ColorF.FromHsl(h, s, l);
+      return new Color((byte) (ColorF.R * (float) byte.MaxValue), (byte) (ColorF.G * (float) byte.MaxValue), (byte) (ColorF.B * (float) byte.MaxValue), a);
+    }
+
+    /// <param name="h">The hue value.</param>
+    /// <param name="s">The saturation value.</param>
+    /// <param name="v">The value/brightness value.</param>
+    /// <param name="a">The alpha value.</param>
+    /// <summary>Creates a color from the specified hue, saturation, value/brightness and alpha values.</summary>
+    /// <returns>The new <see cref="T:SkiaSharp.Color" /> instance.</returns>
+    public static Color FromHsv(float h, float s, float v, byte a = 255)
+    {
+      ColorF ColorF = ColorF.FromHsv(h, s, v);
+      return new Color((byte) (ColorF.R * (float) byte.MaxValue), (byte) (ColorF.G * (float) byte.MaxValue), (byte) (ColorF.B * (float) byte.MaxValue), a);
+    }
+
+    /// <param name="h">The hue value.</param>
+    /// <param name="s">The saturation value.</param>
+    /// <param name="l">The lightness/luminosity value.</param>
+    /// <summary>Converts the current color into it's hue, saturation and lightness/luminosity values.</summary>
+    /// <remarks>The alpha value is separate from the HSL calculation and will always be the same as <see cref="P:SkiaSharp.Color.Alpha" />.</remarks>
+    public void ToHsl(out float h, out float s, out float l) => new ColorF((float) this.R / (float) byte.MaxValue, (float) this.G / (float) byte.MaxValue, (float) this.B / (float) byte.MaxValue).ToHsl(out h, out s, out l);
+
+    /// <param name="h">The hue value.</param>
+    /// <param name="s">The saturation value.</param>
+    /// <param name="v">The value/brightness value.</param>
+    /// <summary>Converts the current color into it's hue, saturation and value/brightness values.</summary>
+    /// <remarks>The alpha value is separate from the HSV/HSB calculation and will always be the same as <see cref="P:SkiaSharp.Color.Alpha" />.</remarks>
+    public void ToHsv(out float h, out float s, out float v) => new ColorF((float) this.R / (float) byte.MaxValue, (float) this.G / (float) byte.MaxValue, (float) this.B / (float) byte.MaxValue).ToHsv(out h, out s, out v);
+
+    /// <summary>Returns the color as a string in the format: #AARRGGBB.</summary>
+    /// <returns />
+    /// <remarks />
+    public override string ToString() =>
+        $"#{(object)this.A:x2}{(object)this.R:x2}{(object)this.G:x2}{(object)this.B:x2}";
+
+    /// <param name="obj">The color to compare with the current color.</param>
+    /// <summary>Determines whether the specified object is equal to the current object.</summary>
+    /// <returns>Returns <see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />.</returns>
+    public bool Equals(Color obj) => (int) obj._colorValue == (int) this._colorValue;
+
+    /// <param name="other">The object to compare with the current object.</param>
+    /// <summary>Determines whether the specified object is equal to the current object.</summary>
+    /// <returns>Returns <see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />.</returns>
+    public override bool Equals(object other) => other is Color Color && this.Equals(Color);
+
+    /// <param name="left">The first color to compare.</param>
+    /// <param name="right">The second color to compare.</param>
+    /// <summary>Indicates whether two <see cref="T:SkiaSharp.Color" /> objects are equal.</summary>
+    /// <returns>Returns <see langword="true" /> if <paramref name="left" /> is equal to <paramref name="right" />, otherwise <see langword="false" />.</returns>
+    public static bool operator ==(Color left, Color right) => left.Equals(right);
+
+    /// <param name="left">The first color to compare.</param>
+    /// <param name="right">The second color to compare.</param>
+    /// <summary>Indicates whether two <see cref="T:SkiaSharp.Color" /> objects are different.</summary>
+    /// <returns>Returns <see langword="true" /> if <paramref name="left" /> is not equal to <paramref name="right" />, otherwise <see langword="false" />.</returns>
+    public static bool operator !=(Color left, Color right) => !left.Equals(right);
+
+    /// <summary>Serves as the default hash function.</summary>
+    /// <returns>Returns a hash code for the current object.</returns>
+    public override int GetHashCode() => this._colorValue.GetHashCode();
+
+    /// <param name="color">The UInt32 representation of a color.</param>
+    /// <summary>Converts a UInt32 to a <see cref="T:SkiaSharp.Color" />.</summary>
+    /// <returns>The new <see cref="T:SkiaSharp.Color" /> instance.</returns>
+    public static implicit operator Color(uint color) => new Color(color);
+
+    /// <param name="color">The color to convert.</param>
+    /// <summary>Converts a <see cref="T:SkiaSharp.Color" /> to a UInt32.</summary>
+    /// <returns>The UInt32 value for the color.</returns>
+    public static explicit operator uint(Color color) => color._colorValue;
+
+    /// <param name="hexString">The hexadecimal string representation of a color.</param>
+    /// <summary>Converts the hexadecimal string representation of a color to its <see cref="T:SkiaSharp.Color" /> equivalent.</summary>
+    /// <returns>The new <see cref="T:SkiaSharp.Color" /> instance.</returns>
+    /// <remarks>This method can parse a string in the forms with or without a preceding '#' character: AARRGGB, RRGGBB, ARGB, RGB.</remarks>
+    public static Color Parse(string hexString)
+    {
+      Color color;
+      if (!Color.TryParse(hexString, out color))
+        throw new ArgumentException("Invalid hexadecimal color string.", nameof (hexString));
+      return color;
+    }
+
+    /// <param name="hexString">The hexadecimal string representation of a color.</param>
+    /// <param name="color">The new <see cref="T:SkiaSharp.Color" /> instance.</param>
+    /// <summary>Converts the hexadecimal string representation of a color to its <see cref="T:SkiaSharp.Color" /> equivalent.</summary>
+    /// <returns>Returns true if the conversion was successful, otherwise false.</returns>
+    /// <remarks>This method can parse a string in the forms with or without a preceding '#' character: AARRGGB, RRGGBB, ARGB, RGB.</remarks>
+    public static bool TryParse(string hexString, out Color color)
+    {
+      if (string.IsNullOrWhiteSpace(hexString))
+      {
+        color = Color.Empty;
+        return false;
+      }
+      hexString = hexString.Trim().ToUpperInvariant();
+      if (hexString[0] == '#')
+        hexString = hexString.Substring(1);
+      int length = hexString.Length;
+      switch (length)
+      {
+        case 3:
+        case 4:
+          byte result1;
+          if (length == 4)
+          {
+            if (!byte.TryParse(hexString[length - 4].ToString() + (object) hexString[length - 4], NumberStyles.HexNumber, (IFormatProvider)CultureInfo.InvariantCulture, out result1))
+            {
+              color = Color.Empty;
+              return false;
+            }
+          }
+          else
+          {
+              result1 = byte.MaxValue;
+          }
+
+          if (!byte.TryParse(hexString[length - 3].ToString() + (object)hexString[length - 3], NumberStyles.HexNumber, (IFormatProvider)CultureInfo.InvariantCulture, out var result2) || !byte.TryParse(hexString[length - 2].ToString() + (object) hexString[length - 2], NumberStyles.HexNumber, (IFormatProvider) CultureInfo.InvariantCulture, out var result3) || !byte.TryParse(hexString[length - 1].ToString() + (object) hexString[length - 1], NumberStyles.HexNumber, (IFormatProvider) CultureInfo.InvariantCulture, out var result4))
+          {
+            color = Color.Empty;
+            return false;
+          }
+          color = new Color(result2, result3, result4, result1);
+          return true;
+        case 6:
+        case 8: if (!uint.TryParse(hexString, NumberStyles.HexNumber, (IFormatProvider)CultureInfo.InvariantCulture, out var result5))
+          {
+            color = Color.Empty;
+            return false;
+          }
+          color = (Color)result5;
+          if (length == 6)
+            color = color.WithAlpha(byte.MaxValue);
+          return true;
+        default:
+          color = Empty;
+          return false;
+      }
+    }
+  }
+}

+ 400 - 0
src/PixiEditor.DrawingApi.Core/ColorsImpl/ColorF.cs

@@ -0,0 +1,400 @@
+using System;
+using PixiEditor.DrawingApi.Core.Bridge;
+
+namespace PixiEditor.DrawingApi.Core.ColorsImpl
+{
+    public readonly struct ColorF : IEquatable<ColorF>
+    {
+        private const float Epsilon = 0.001f;
+
+        /// <summary>Gets an "empty" color, with zero for all the components.</summary>
+        public static readonly ColorF Empty;
+        
+        private readonly float _fR;
+        private readonly float _fG;
+        private readonly float _fB;
+        private readonly float _fA;
+
+        public ColorF(float r, float g, float b)
+        {
+            _fR = r;
+            _fG = g;
+            _fB = b;
+            _fA = 1f;
+        }
+
+        public ColorF(float r, float g, float b, float a)
+        {
+            _fR = r;
+            _fG = g;
+            _fB = b;
+            _fA = a;
+        }
+
+        /// <summary>Gets the hue value.</summary>
+        /// <value />
+        public float Hue
+        {
+            get
+            {
+                float h;
+                ToHsv(out h, out var _, out var _);
+                return h;
+            }
+        }
+
+        /// <summary>Gets the red component of the color.</summary>
+        /// <value />
+        public float R => _fR;
+
+        /// <summary>Gets the green component of the color.</summary>
+        /// <value />
+
+        public float G => _fG;
+
+        /// <summary>Gets the blue component of the color.</summary>
+        /// <value />
+
+        public float B => _fB;
+
+        /// <summary>Gets the alpha component of the color.</summary>
+        /// <value />
+
+        public float A => _fA;
+
+        /// <param name="obj">The color to compare with the current color.</param>
+        /// <summary>Determines whether the specified object is equal to the current object.</summary>
+        /// <returns>
+        ///     Returns <see langword="true" /> if the specified object is equal to the current object; otherwise,
+        ///     <see langword="false" />.
+        /// </returns>
+        /// 
+        public bool Equals(ColorF obj)
+        {
+            return R == (double)obj._fR && G == (double)obj._fG && B == (double)obj._fB &&
+                   A == (double)obj._fA;
+        }
+
+        /// <param name="red">The new red component.</param>
+        /// <summary>Returns a new color based on this current instance, but with the new red channel value.</summary>
+        /// <returns />
+        public ColorF WithRed(float red)
+        {
+            return new ColorF(red, G, B, A);
+        }
+
+        /// <param name="green">The new green component.</param>
+        /// <summary>Returns a new color based on this current instance, but with the new green channel value.</summary>
+        /// <returns />
+        public ColorF WithGreen(float green)
+        {
+            return new ColorF(R, green, B, A);
+        }
+
+        /// <param name="blue">The new blue component.</param>
+        /// <summary>Returns a new color based on this current instance, but with the new blue channel value.</summary>
+        public ColorF WithBlue(float blue)
+        {
+            return new ColorF(R, G, blue, A);
+        }
+
+        /// <param name="alpha">The new alpha component.</param>
+        /// <summary>Returns a new color based on this current instance, but with the new alpha channel value.</summary>
+        public ColorF WithAlpha(float alpha)
+        {
+            return new ColorF(R, G, B, alpha);
+        }
+
+        /// <summary>Clamp the color components in the range [0..1].</summary>
+        /// <returns>Returns the clamped color.</returns>
+        public ColorF Clamp()
+        {
+            return new ColorF(Clamp(R), Clamp(G), Clamp(B), Clamp(A));
+
+            static float Clamp(float v)
+            {
+                if (v > 1.0)
+                {
+                    return 1f;
+                }
+
+                return v < 0.0 ? 0.0f : v;
+            }
+        }
+
+        /// <param name="h">The hue value.</param>
+        /// <param name="s">The saturation value.</param>
+        /// <param name="l">The lightness/luminosity value.</param>
+        /// <param name="a">The alpha value.</param>
+        /// <summary>Creates a color from the specified hue, saturation, lightness/luminosity and alpha values.</summary>
+        /// <returns>The new <see cref="T:DrawingApiCore.ColorF" /> instance.</returns>
+        public static ColorF FromHsl(float h, float s, float l, float a = 1f)
+        {
+            h /= 360f;
+            s /= 100f;
+            l /= 100f;
+            var red = l;
+            var green = l;
+            var blue = l;
+            if (Math.Abs(s) > 1.0 / 1000.0)
+            {
+                var v2 = l >= 0.5 ? (float)(l + (double)s - (s * (double)l)) : l * (1f + s);
+                var v1 = (2f * l) - v2;
+                red = HueToRgb(v1, v2, h + 0.33333334f);
+                green = HueToRgb(v1, v2, h);
+                blue = HueToRgb(v1, v2, h - 0.33333334f);
+            }
+
+            return new ColorF(red, green, blue, a);
+        }
+
+        private static float HueToRgb(float v1, float v2, float vH)
+        {
+            if (vH < 0.0)
+            {
+                ++vH;
+            }
+
+            if (vH > 1.0)
+            {
+                --vH;
+            }
+
+            if (6.0 * vH < 1.0)
+            {
+                return v1 + ((float)((v2 - (double)v1) * 6.0) * vH);
+            }
+
+            if (2.0 * vH < 1.0)
+            {
+                return v2;
+            }
+
+            return 3.0 * vH < 2.0 ? v1 + (float)((v2 - (double)v1) * (0.6666666865348816 - vH) * 6.0) : v1;
+        }
+
+        /// <param name="h">The hue value.</param>
+        /// <param name="s">The saturation value.</param>
+        /// <param name="v">The value/brightness value.</param>
+        /// <param name="a">The alpha value.</param>
+        /// <summary>Creates a color from the specified hue, saturation, value/brightness and alpha values.</summary>
+        /// <returns>The new <see cref="T:DrawingApiCore.ColorF" /> instance.</returns>
+        public static ColorF FromHsv(float h, float s, float v, float a = 1f)
+        {
+            h /= 360f;
+            s /= 100f;
+            v /= 100f;
+            var red = v;
+            var green = v;
+            var blue = v;
+            if (Math.Abs(s) > 1.0 / 1000.0)
+            {
+                h *= 6f;
+                if (Math.Abs(h - 6f) < 1.0 / 1000.0)
+                {
+                    h = 0.0f;
+                }
+
+                var num1 = (int)h;
+                var num2 = v * (1f - s);
+                var num3 = v * (float)(1.0 - (s * (h - (double)num1)));
+                var num4 = v * (float)(1.0 - (s * (1.0 - (h - (double)num1))));
+                switch (num1)
+                {
+                    case 0:
+                        red = v;
+                        green = num4;
+                        blue = num2;
+                        break;
+                    case 1:
+                        red = num3;
+                        green = v;
+                        blue = num2;
+                        break;
+                    case 2:
+                        red = num2;
+                        green = v;
+                        blue = num4;
+                        break;
+                    case 3:
+                        red = num2;
+                        green = num3;
+                        blue = v;
+                        break;
+                    case 4:
+                        red = num4;
+                        green = num2;
+                        blue = v;
+                        break;
+                    default:
+                        red = v;
+                        green = num2;
+                        blue = num3;
+                        break;
+                }
+            }
+
+            return new ColorF(red, green, blue, a);
+        }
+
+        /// <param name="h">The hue value.</param>
+        /// <param name="s">The saturation value.</param>
+        /// <param name="l">The lightness/luminosity value.</param>
+        /// <summary>Converts the current color into it's hue, saturation and lightness/luminosity values.</summary>
+        /// <remarks>
+        ///     The alpha value is separate from the HSL calculation and will always be the same as
+        ///     <see cref="P:DrawingApiCore.ColorF.Alpha" />.
+        /// </remarks>
+        public void ToHsl(out float h, out float s, out float l)
+        {
+            var fR = R;
+            var fG = G;
+            var fB = B;
+            var num1 = Math.Min(Math.Min(fR, fG), fB);
+            var num2 = Math.Max(Math.Max(fR, fG), fB);
+            var num3 = num2 - num1;
+            h = 0.0f;
+            s = 0.0f;
+            l = (float)((num2 + (double)num1) / 2.0);
+            if (Math.Abs(num3) > 1.0 / 1000.0)
+            {
+                s = l >= 0.5 ? num3 / (2f - num2 - num1) : num3 / (num2 + num1);
+                var num4 = (float)(((num2 - (double)fR) / 6.0) + (num3 / 2.0)) / num3;
+                var num5 = (float)(((num2 - (double)fG) / 6.0) + (num3 / 2.0)) / num3;
+                var num6 = (float)(((num2 - (double)fB) / 6.0) + (num3 / 2.0)) / num3;
+                h = Math.Abs(fR - num2) >= 1.0 / 1000.0
+                    ? Math.Abs(fG - num2) >= 1.0 / 1000.0 ? 0.6666667f + num5 - num4 : 0.33333334f + num4 - num6
+                    : num6 - num5;
+                if (h < 0.0)
+                {
+                    ++h;
+                }
+
+                if (h > 1.0)
+                {
+                    --h;
+                }
+            }
+
+            h *= 360f;
+            s *= 100f;
+            l *= 100f;
+        }
+
+        /// <param name="h">The hue value.</param>
+        /// <param name="s">The saturation value.</param>
+        /// <param name="v">The value/brightness value.</param>
+        /// <summary>Converts the current color into it's hue, saturation and value/brightness values.</summary>
+        /// <remarks>
+        ///     The alpha value is separate from the HSV/HSB calculation and will always be the same as
+        ///     <see cref="P:DrawingApiCore.ColorF.Alpha" />.
+        /// </remarks>
+        public void ToHsv(out float h, out float s, out float v)
+        {
+            var fR = R;
+            var fG = G;
+            var fB = B;
+            var num1 = Math.Min(Math.Min(fR, fG), fB);
+            var num2 = Math.Max(Math.Max(fR, fG), fB);
+            var num3 = num2 - num1;
+            h = 0.0f;
+            s = 0.0f;
+            v = num2;
+            if (Math.Abs(num3) > 1.0 / 1000.0)
+            {
+                s = num3 / num2;
+                var num4 = (float)(((num2 - (double)fR) / 6.0) + (num3 / 2.0)) / num3;
+                var num5 = (float)(((num2 - (double)fG) / 6.0) + (num3 / 2.0)) / num3;
+                var num6 = (float)(((num2 - (double)fB) / 6.0) + (num3 / 2.0)) / num3;
+                h = Math.Abs(fR - num2) >= 1.0 / 1000.0
+                    ? Math.Abs(fG - num2) >= 1.0 / 1000.0 ? 0.6666667f + num5 - num4 : 0.33333334f + num4 - num6
+                    : num6 - num5;
+                if (h < 0.0)
+                {
+                    ++h;
+                }
+
+                if (h > 1.0)
+                {
+                    --h;
+                }
+            }
+
+            h *= 360f;
+            s *= 100f;
+            v *= 100f;
+        }
+
+        /// <summary>Returns the color as a string in the format: #AARRGGBB.</summary>
+        /// <remarks>As a result of converting a floating-point color to an integer color, some data loss will occur.</remarks>
+        public override string ToString()
+        {
+            return ((Color)this).ToString();
+        }
+
+        /// <param name="color">The <see cref="T:DrawingApiCore.SKColor" />.</param>
+        /// <summary>Converts a <see cref="T:DrawingApiCore.SKColor" /> to a <see cref="T:DrawingApiCore.ColorF" />.</summary>
+        /// <returns>The new <see cref="T:DrawingApiCore.ColorF" /> instance.</returns>
+        public static implicit operator ColorF(Color color)
+        {
+            return DrawingBackendApi.Current.ColorImplementation.ColorToColorF((uint)color);
+        }
+
+        /// <param name="color">The color to convert.</param>
+        /// <summary>Converts a <see cref="T:DrawingApiCore.ColorF" /> to a <see cref="T:DrawingApiCore.SKColor" />.</summary>
+        /// <returns>The <see cref="T:DrawingApiCore.SKColor" />.</returns>
+        /// <remarks>As a result of converting a floating-point color to an integer color, some data loss will occur.</remarks>
+        public static explicit operator Color(ColorF color)
+        {
+            return DrawingBackendApi.Current.ColorImplementation.ColorFToColor(color);
+        }
+
+        /// <param name="obj">The object to compare with the current object.</param>
+        /// <summary>Determines whether the specified object is equal to the current object.</summary>
+        /// <returns>
+        ///     Returns <see langword="true" /> if the specified object is equal to the current object; otherwise,
+        ///     <see langword="false" />.
+        /// </returns>
+        public override bool Equals(object obj)
+        {
+            return obj is ColorF colorF && Equals(colorF);
+        }
+
+        /// <param name="left">The first color to compare.</param>
+        /// <param name="right">The second color to compare.</param>
+        /// <summary>Indicates whether two <see cref="T:DrawingApiCore.ColorF" /> objects are equal.</summary>
+        /// <returns>
+        ///     Returns <see langword="true" /> if <paramref name="left" /> is equal to <paramref name="right" />, otherwise
+        ///     <see langword="false" />.
+        /// </returns>
+        public static bool operator ==(ColorF left, ColorF right)
+        {
+            return left.Equals(right);
+        }
+
+        /// <param name="left">The first color to compare.</param>
+        /// <param name="right">The second color to compare.</param>
+        /// <summary>Indicates whether two <see cref="T:DrawingApiCore.ColorF" /> objects are different.</summary>
+        /// <returns>
+        ///     Returns <see langword="true" /> if <paramref name="left" /> is not equal to <paramref name="right" />,
+        ///     otherwise <see langword="false" />.
+        /// </returns>
+        public static bool operator !=(ColorF left, ColorF right)
+        {
+            return !left.Equals(right);
+        }
+
+        /// <summary>Serves as the default hash function.</summary>
+        /// <returns>Returns a hash code for the current object.</returns>
+        public override int GetHashCode()
+        {
+            var hashCode = new HashCode();
+            hashCode.Add(R);
+            hashCode.Add(G);
+            hashCode.Add(B);
+            hashCode.Add(A);
+            return hashCode.ToHashCode();
+        }
+    }
+}
+

+ 436 - 0
src/PixiEditor.DrawingApi.Core/ColorsImpl/Colors.cs

@@ -0,0 +1,436 @@
+using System.Runtime.InteropServices;
+
+namespace PixiEditor.DrawingApi.Core.ColorsImpl;
+
+/// <summary>Definitions for some common color names.</summary>
+[StructLayout(LayoutKind.Sequential, Size = 1)]
+public struct Colors
+{
+    /// <summary>Gets the predefined color of alice blue, or #FFF0F8FF.</summary>
+    public static Color AliceBlue = new(4293982463U);
+
+    /// <summary>Gets the predefined color of antique white, or #FFFAEBD7.</summary>
+    public static Color AntiqueWhite = new(4294634455U);
+
+    /// <summary>Gets the predefined color of aqua, or #FF00FFFF.</summary>
+    public static Color Aqua = new(4278255615U);
+
+    /// <summary>Gets the predefined color of aquamarine, or #FF7FFFD4.</summary>
+    public static Color Aquamarine = new(4286578644U);
+
+    /// <summary>Gets the predefined color of azure, or #FFF0FFFF.</summary>
+    public static Color Azure = new(4293984255U);
+
+    /// <summary>Gets the predefined color of beige, or #FFF5F5DC.</summary>
+    public static Color Beige = new(4294309340U);
+
+    /// <summary>Gets the predefined color of bisque, or #FFFFE4C4.</summary>
+    public static Color Bisque = new(4294960324U);
+
+    /// <summary>Gets the predefined color of black, or #FF000000.</summary>
+    public static Color Black = new(4278190080U);
+
+    /// <summary>Gets the predefined color of blanched almond, or #FFFFEBCD.</summary>
+    public static Color BlanchedAlmond = new(4294962125U);
+
+    /// <summary>Gets the predefined color of blue, or #FF0000FF.</summary>
+    public static Color Blue = new(4278190335U);
+
+    /// <summary>Gets the predefined color of blue violet, or #FF8A2BE2.</summary>
+    public static Color BlueViolet = new(4287245282U);
+
+    /// <summary>Gets the predefined color of brown, or #FFA52A2A.</summary>
+    public static Color Brown = new(4289014314U);
+
+    /// <summary>Gets the predefined color of burly wood, or #FFDEB887.</summary>
+    public static Color BurlyWood = new(4292786311U);
+
+    /// <summary>Gets the predefined color of cadet blue, or #FF5F9EA0.</summary>
+    public static Color CadetBlue = new(4284456608U);
+
+    /// <summary>Gets the predefined color of chartreuse, or #FF7FFF00.</summary>
+    public static Color Chartreuse = new(4286578432U);
+
+    /// <summary>Gets the predefined color of chocolate, or #FFD2691E.</summary>
+    public static Color Chocolate = new(4291979550U);
+
+    /// <summary>Gets the predefined color of coral, or #FFFF7F50.</summary>
+    public static Color Coral = new(4294934352U);
+
+    /// <summary>Gets the predefined color of cornflower blue, or #FF6495ED.</summary>
+    public static Color CornflowerBlue = new(4284782061U);
+
+    /// <summary>Gets the predefined color of cornsilk, or #FFFFF8DC.</summary>
+    public static Color Cornsilk = new(4294965468U);
+
+    /// <summary>Gets the predefined color of crimson, or #FFDC143C.</summary>
+    public static Color Crimson = new(4292613180U);
+
+    /// <summary>Gets the predefined color of cyan, or #FF00FFFF.</summary>
+    public static Color Cyan = new(4278255615U);
+
+    /// <summary>Gets the predefined color of dark blue, or #FF00008B.</summary>
+    public static Color DarkBlue = new(4278190219U);
+
+    /// <summary>Gets the predefined color of dark cyan, or #FF008B8B.</summary>
+    public static Color DarkCyan = new(4278225803U);
+
+    /// <summary>Gets the predefined color of dark goldenrod, or #FFB8860B.</summary>
+    public static Color DarkGoldenrod = new(4290283019U);
+
+    /// <summary>Gets the predefined color of dark gray, or #FFA9A9A9.</summary>
+    public static Color DarkGray = new(4289309097U);
+
+    /// <summary>Gets the predefined color of dark green, or #FF006400.</summary>
+    public static Color DarkGreen = new(4278215680U);
+
+    /// <summary>Gets the predefined color of dark khaki, or #FFBDB76B.</summary>
+    public static Color DarkKhaki = new(4290623339U);
+
+    /// <summary>Gets the predefined color of dark magenta, or #FF8B008B.</summary>
+    public static Color DarkMagenta = new(4287299723U);
+
+    /// <summary>Gets the predefined color of dark olive green, or #FF556B2F.</summary>
+    public static Color DarkOliveGreen = new(4283788079U);
+
+    /// <summary>Gets the predefined color of dark orange, or #FFFF8C00.</summary>
+    public static Color DarkOrange = new(4294937600U);
+
+    /// <summary>Gets the predefined color of dark orchid, or #FF9932CC.</summary>
+    public static Color DarkOrchid = new(4288230092U);
+
+    /// <summary>Gets the predefined color of dark red, or #FF8B0000.</summary>
+    public static Color DarkRed = new(4287299584U);
+
+    /// <summary>Gets the predefined color of dark salmon, or #FFE9967A.</summary>
+    public static Color DarkSalmon = new(4293498490U);
+
+    /// <summary>Gets the predefined color of dark sea green, or #FF8FBC8B.</summary>
+    public static Color DarkSeaGreen = new(4287609995U);
+
+    /// <summary>Gets the predefined color of dark slate blue, or #FF483D8B.</summary>
+    public static Color DarkSlateBlue = new(4282924427U);
+
+    /// <summary>Gets the predefined color of dark slate gray, or #FF2F4F4F.</summary>
+    public static Color DarkSlateGray = new(4281290575U);
+
+    /// <summary>Gets the predefined color of dark turquoise, or #FF00CED1.</summary>
+    public static Color DarkTurquoise = new(4278243025U);
+
+    /// <summary>Gets the predefined color of dark violet, or #FF9400D3.</summary>
+    public static Color DarkViolet = new(4287889619U);
+
+    /// <summary>Gets the predefined color of deep pink, or #FFFF1493.</summary>
+    public static Color DeepPink = new(4294907027U);
+
+    /// <summary>Gets the predefined color of deep sky blue, or #FF00BFFF.</summary>
+    public static Color DeepSkyBlue = new(4278239231U);
+
+    /// <summary>Gets the predefined color of dim gray, or #FF696969.</summary>
+    public static Color DimGray = new(4285098345U);
+
+    /// <summary>Gets the predefined color of dodger blue, or #FF1E90FF.</summary>
+    public static Color DodgerBlue = new(4280193279U);
+
+    /// <summary>Gets the predefined color of firebrick, or #FFB22222.</summary>
+    public static Color Firebrick = new(4289864226U);
+
+    /// <summary>Gets the predefined color of floral white, or #FFFFFAF0.</summary>
+    public static Color FloralWhite = new(4294966000U);
+
+    /// <summary>Gets the predefined color of forest green, or #FF228B22.</summary>
+    public static Color ForestGreen = new(4280453922U);
+
+    /// <summary>Gets the predefined color of fuchsia, or #FFFF00FF.</summary>
+    public static Color Fuchsia = new(4294902015U);
+
+    /// <summary>Gets the predefined color of gainsboro, or #FFDCDCDC.</summary>
+    public static Color Gainsboro = new(4292664540U);
+
+    /// <summary>Gets the predefined color of ghost white, or #FFF8F8FF.</summary>
+    public static Color GhostWhite = new(4294506751U);
+
+    /// <summary>Gets the predefined color of gold, or #FFFFD700.</summary>
+    public static Color Gold = new(4294956800U);
+
+    /// <summary>Gets the predefined color of goldenrod, or #FFDAA520.</summary>
+    public static Color Goldenrod = new(4292519200U);
+
+    /// <summary>Gets the predefined color of gray, or #FF808080.</summary>
+    public static Color Gray = new(4286611584U);
+
+    /// <summary>Gets the predefined color of green, or #FF008000.</summary>
+    public static Color Green = new(4278222848U);
+
+    /// <summary>Gets the predefined color of green yellow, or #FFADFF2F.</summary>
+    public static Color GreenYellow = new(4289593135U);
+
+    /// <summary>Gets the predefined color of honeydew, or #FFF0FFF0.</summary>
+    public static Color Honeydew = new(4293984240U);
+
+    /// <summary>Gets the predefined color of hot pink, or #FFFF69B4.</summary>
+    public static Color HotPink = new(4294928820U);
+
+    /// <summary>Gets the predefined color of indian red, or #FFCD5C5C.</summary>
+    public static Color IndianRed = new(4291648604U);
+
+    /// <summary>Gets the predefined color of indigo, or #FF4B0082.</summary>
+    public static Color Indigo = new(4283105410U);
+
+    /// <summary>Gets the predefined color of ivory, or #FFFFFFF0.</summary>
+    public static Color Ivory = new(4294967280U);
+
+    /// <summary>Gets the predefined color of khaki, or #FFF0E68C.</summary>
+    public static Color Khaki = new(4293977740U);
+
+    /// <summary>Gets the predefined color of lavender, or #FFE6E6FA.</summary>
+    public static Color Lavender = new(4293322490U);
+
+    /// <summary>Gets the predefined color of lavender blush, or #FFFFF0F5.</summary>
+    public static Color LavenderBlush = new(4294963445U);
+
+    /// <summary>Gets the predefined color of lawn green, or #FF7CFC00.</summary>
+    public static Color LawnGreen = new(4286381056U);
+
+    /// <summary>Gets the predefined color of lemon chiffon, or #FFFFFACD.</summary>
+    public static Color LemonChiffon = new(4294965965U);
+
+    /// <summary>Gets the predefined color of light blue, or #FFADD8E6.</summary>
+    public static Color LightBlue = new(4289583334U);
+
+    /// <summary>Gets the predefined color of light coral, or #FFF08080.</summary>
+    public static Color LightCoral = new(4293951616U);
+
+    /// <summary>Gets the predefined color of light cyan, or #FFE0FFFF.</summary>
+    public static Color LightCyan = new(4292935679U);
+
+    /// <summary>Gets the predefined color of light goldenrod yellow, or #FFFAFAD2.</summary>
+    public static Color LightGoldenrodYellow = new(4294638290U);
+
+    /// <summary>Gets the predefined color of light gray, or #FFD3D3D3.</summary>
+    public static Color LightGray = new(4292072403U);
+
+    /// <summary>Gets the predefined color of light green, or #FF90EE90.</summary>
+    public static Color LightGreen = new(4287688336U);
+
+    /// <summary>Gets the predefined color of light pink, or #FFFFB6C1.</summary>
+    public static Color LightPink = new(4294948545U);
+
+    /// <summary>Gets the predefined color of light salmon, or #FFFFA07A.</summary>
+    public static Color LightSalmon = new(4294942842U);
+
+    /// <summary>Gets the predefined color of light sea green, or #FF20B2AA.</summary>
+    public static Color LightSeaGreen = new(4280332970U);
+
+    /// <summary>Gets the predefined color of light sky blue, or #FF87CEFA.</summary>
+    public static Color LightSkyBlue = new(4287090426U);
+
+    /// <summary>Gets the predefined color of light slate gray, or #FF778899.</summary>
+    public static Color LightSlateGray = new(4286023833U);
+
+    /// <summary>Gets the predefined color of light steel blue, or #FFB0C4DE.</summary>
+    public static Color LightSteelBlue = new(4289774814U);
+
+    /// <summary>Gets the predefined color of light yellow, or #FFFFFFE0.</summary>
+    public static Color LightYellow = new(4294967264U);
+
+    /// <summary>Gets the predefined color of lime, or #FF00FF00.</summary>
+    public static Color Lime = new(4278255360U);
+
+    /// <summary>Gets the predefined color of lime green, or #FF32CD32.</summary>
+    public static Color LimeGreen = new(4281519410U);
+
+    /// <summary>Gets the predefined color of linen, or #FFFAF0E6.</summary>
+    public static Color Linen = new(4294635750U);
+
+    /// <summary>Gets the predefined color of magenta, or #FFFF00FF.</summary>
+    public static Color Magenta = new(4294902015U);
+
+    /// <summary>Gets the predefined color of maroon, or #FF800000.</summary>
+    public static Color Maroon = new(4286578688U);
+
+    /// <summary>Gets the predefined color of medium aquamarine, or #FF66CDAA.</summary>
+    public static Color MediumAquamarine = new(4284927402U);
+
+    /// <summary>Gets the predefined color of medium blue, or #FF0000CD.</summary>
+    public static Color MediumBlue = new(4278190285U);
+
+    /// <summary>Gets the predefined color of medium orchid, or #FFBA55D3.</summary>
+    public static Color MediumOrchid = new(4290401747U);
+
+    /// <summary>Gets the predefined color of medium purple, or #FF9370DB.</summary>
+    public static Color MediumPurple = new(4287852763U);
+
+    /// <summary>Gets the predefined color of medium sea green, or #FF3CB371.</summary>
+    public static Color MediumSeaGreen = new(4282168177U);
+
+    /// <summary>Gets the predefined color of medium slate blue, or #FF7B68EE.</summary>
+    public static Color MediumSlateBlue = new(4286277870U);
+
+    /// <summary>Gets the predefined color of medium spring green, or #FF00FA9A.</summary>
+    public static Color MediumSpringGreen = new(4278254234U);
+
+    /// <summary>Gets the predefined color of medium turquoise, or #FF48D1CC.</summary>
+    public static Color MediumTurquoise = new(4282962380U);
+
+    /// <summary>Gets the predefined color of medium violet red, or #FFC71585.</summary>
+    public static Color MediumVioletRed = new(4291237253U);
+
+    /// <summary>Gets the predefined color of midnight blue, or #FF191970.</summary>
+    public static Color MidnightBlue = new(4279834992U);
+
+    /// <summary>Gets the predefined color of mint cream, or #FFF5FFFA.</summary>
+    public static Color MintCream = new(4294311930U);
+
+    /// <summary>Gets the predefined color of misty rose, or #FFFFE4E1.</summary>
+    public static Color MistyRose = new(4294960353U);
+
+    /// <summary>Gets the predefined color of moccasin, or #FFFFE4B5.</summary>
+    public static Color Moccasin = new(4294960309U);
+
+    /// <summary>Gets the predefined color of navajo white, or #FFFFDEAD.</summary>
+    public static Color NavajoWhite = new(4294958765U);
+
+    /// <summary>Gets the predefined color of navy, or #FF000080.</summary>
+    public static Color Navy = new(4278190208U);
+
+    /// <summary>Gets the predefined color of old lace, or #FFFDF5E6.</summary>
+    public static Color OldLace = new(4294833638U);
+
+    /// <summary>Gets the predefined color of olive, or #FF808000.</summary>
+    public static Color Olive = new(4286611456U);
+
+    /// <summary>Gets the predefined color of olive drab, or #FF6B8E23.</summary>
+    public static Color OliveDrab = new(4285238819U);
+
+    /// <summary>Gets the predefined color of orange, or #FFFFA500.</summary>
+    public static Color Orange = new(4294944000U);
+
+    /// <summary>Gets the predefined color of orange red, or #FFFF4500.</summary>
+    public static Color OrangeRed = new(4294919424U);
+
+    /// <summary>Gets the predefined color of orchid, or #FFDA70D6.</summary>
+    public static Color Orchid = new(4292505814U);
+
+    /// <summary>Gets the predefined color of pale goldenrod, or #FFEEE8AA.</summary>
+    public static Color PaleGoldenrod = new(4293847210U);
+
+    /// <summary>Gets the predefined color of pale green, or #FF98FB98.</summary>
+    public static Color PaleGreen = new(4288215960U);
+
+    /// <summary>Gets the predefined color of pale turquoise, or #FFAFEEEE.</summary>
+    public static Color PaleTurquoise = new(4289720046U);
+
+    /// <summary>Gets the predefined color of pale violet red, or #FFDB7093.</summary>
+    public static Color PaleVioletRed = new(4292571283U);
+
+    /// <summary>Gets the predefined color of papaya whip, or #FFFFEFD5.</summary>
+    public static Color PapayaWhip = new(4294963157U);
+
+    /// <summary>Gets the predefined color of peach puff, or #FFFFDAB9.</summary>
+    public static Color PeachPuff = new(4294957753U);
+
+    /// <summary>Gets the predefined color of peru, or #FFCD853F.</summary>
+    public static Color Peru = new(4291659071U);
+
+    /// <summary>Gets the predefined color of pink, or #FFFFC0CB.</summary>
+    public static Color Pink = new(4294951115U);
+
+    /// <summary>Gets the predefined color of plum, or #FFDDA0DD.</summary>
+    public static Color Plum = new(4292714717U);
+
+    /// <summary>Gets the predefined color of powder blue, or #FFB0E0E6.</summary>
+    public static Color PowderBlue = new(4289781990U);
+
+    /// <summary>Gets the predefined color of purple, or #FF800080.</summary>
+    public static Color Purple = new(4286578816U);
+
+    /// <summary>Gets the predefined color of red, or #FFFF0000.</summary>
+    public static Color Red = new(4294901760U);
+
+    /// <summary>Gets the predefined color of rosy brown, or #FFBC8F8F.</summary>
+    public static Color RosyBrown = new(4290547599U);
+
+    /// <summary>Gets the predefined color of royal blue, or #FF4169E1.</summary>
+    public static Color RoyalBlue = new(4282477025U);
+
+    /// <summary>Gets the predefined color of saddle brown, or #FF8B4513.</summary>
+    public static Color SaddleBrown = new(4287317267U);
+
+    /// <summary>Gets the predefined color of salmon, or #FFFA8072.</summary>
+    public static Color Salmon = new(4294606962U);
+
+    /// <summary>Gets the predefined color of sandy brown, or #FFF4A460.</summary>
+    public static Color SandyBrown = new(4294222944U);
+
+    /// <summary>Gets the predefined color of sea green, or #FF2E8B57.</summary>
+    public static Color SeaGreen = new(4281240407U);
+
+    /// <summary>Gets the predefined color of sea shell, or #FFFFF5EE.</summary>
+    public static Color SeaShell = new(4294964718U);
+
+    /// <summary>Gets the predefined color of sienna, or #FFA0522D.</summary>
+    public static Color Sienna = new(4288696877U);
+
+    /// <summary>Gets the predefined color of silver, or #FFC0C0C0.</summary>
+    public static Color Silver = new(4290822336U);
+
+    /// <summary>Gets the predefined color of sky blue, or #FF87CEEB.</summary>
+    public static Color SkyBlue = new(4287090411U);
+
+    /// <summary>Gets the predefined color of slate blue, or #FF6A5ACD.</summary>
+    public static Color SlateBlue = new(4285160141U);
+
+    /// <summary>Gets the predefined color of slate gray, or #FF708090.</summary>
+    public static Color SlateGray = new(4285563024U);
+
+    /// <summary>Gets the predefined color of snow, or #FFFFFAFA.</summary>
+    public static Color Snow = new(4294966010U);
+
+    /// <summary>Gets the predefined color of spring green, or #FF00FF7F.</summary>
+    public static Color SpringGreen = new(4278255487U);
+
+    /// <summary>Gets the predefined color of steel blue, or #FF4682B4.</summary>
+    public static Color SteelBlue = new(4282811060U);
+
+    /// <summary>Gets the predefined color of tan, or #FFD2B48C.</summary>
+    public static Color Tan = new(4291998860U);
+
+    /// <summary>Gets the predefined color of teal, or #FF008080.</summary>
+    public static Color Teal = new(4278222976U);
+
+    /// <summary>Gets the predefined color of thistle, or #FFD8BFD8.</summary>
+    public static Color Thistle = new(4292394968U);
+
+    /// <summary>Gets the predefined color of tomato, or #FFFF6347.</summary>
+    public static Color Tomato = new(4294927175U);
+
+    /// <summary>Gets the predefined color of turquoise, or #FF40E0D0.</summary>
+    public static Color Turquoise = new(4282441936U);
+
+    /// <summary>Gets the predefined color of violet, or #FFEE82EE.</summary>
+    public static Color Violet = new(4293821166U);
+
+    /// <summary>Gets the predefined color of wheat, or #FFF5DEB3.</summary>
+    public static Color Wheat = new(4294303411U);
+
+    /// <summary>Gets the predefined color of white, or #FFFFFFFF.</summary>
+    public static Color White = new(uint.MaxValue);
+
+    /// <summary>Gets the predefined color of white smoke, or #FFF5F5F5.</summary>
+    public static Color WhiteSmoke = new(4294309365U);
+
+    /// <summary>Gets the predefined color of yellow, or #FFFFFF00.</summary>
+    public static Color Yellow = new(4294967040U);
+
+    /// <summary>Gets the predefined color of yellow green, or #FF9ACD32.</summary>
+    public static Color YellowGreen = new(4288335154U);
+
+    /// <summary>Gets the predefined color of white transparent, or #00FFFFFF.</summary>
+    public static Color Transparent = new(16777215U);
+
+    /// <summary>Gets the predefined empty color (black transparent), or #00000000.</summary>
+    /// <value />
+
+    public static Color Empty => new(0U);
+}

+ 15 - 0
src/PixiEditor.DrawingApi.Core/Exceptions/InitializationDuplicateException.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace PixiEditor.DrawingApi.Core.Exceptions
+{
+    public class InitializationDuplicateException : Exception
+    {
+        public InitializationDuplicateException()
+        {
+        }
+
+        public InitializationDuplicateException(string message) : base(message)
+        {
+        }
+    }
+}

+ 414 - 0
src/PixiEditor.DrawingApi.Core/Numerics/Matrix3X3.cs

@@ -0,0 +1,414 @@
+using System;
+using PixiEditor.DrawingApi.Core.Bridge;
+
+namespace PixiEditor.DrawingApi.Core.Numerics;
+
+/// <summary>A 3x3 transformation matrix with perspective.</summary>
+/// <remarks>
+///     It extends the traditional 2D affine transformation matrix with three perspective components that allow simple
+///     3D effects to be created with it. Those components must be manually set by using the
+///     <see cref="Matrix3X3.Persp0" />, <see cref="Matrix3X3.Persp1" />,
+///     <see cref="Matrix3X3.Persp2" /> fields of the matrix.
+/// </remarks>
+public struct Matrix3X3 : IEquatable<Matrix3X3>
+{
+    internal const float DegreesToRadians = 0.017453292f;
+
+    public static readonly Matrix3X3 Identity = new() { ScaleX = 1f, ScaleY = 1f, Persp2 = 1f };
+    
+    public static readonly Matrix3X3 Empty = default;
+
+    /// <summary>Gets or sets the scaling in the x-direction.</summary>
+    /// <value />
+    public float ScaleX { get; set; }
+
+    /// <summary>Gets or sets the skew in the x-direction.</summary>
+    /// <value />
+    public float SkewX { get; set; }
+
+    /// <summary>Get or sets the translation in the x-direction.</summary>
+    /// <value />
+    public float TransX { get; set; }
+
+    /// <summary>Gets or sets the skew in the y-direction.</summary>
+    /// <value />
+    public float SkewY { get; set; }
+
+    /// <summary>Gets or sets the scaling in the y-direction.</summary>
+    /// <value />
+    public float ScaleY { get; set; }
+
+    /// <summary>Get or sets the translation in the y-direction.</summary>
+    /// <value />
+    public float TransY { get; set; }
+
+    /// <summary>Gets or sets the x-perspective.</summary>
+    /// <value />
+    public float Persp0 { get; set; }
+
+    /// <summary>Gets or sets the y-perspective.</summary>
+    /// <value />
+    public float Persp1 { get; set; }
+
+    /// <summary>Gets or sets the z-perspective.</summary>
+    /// <value />
+    public float Persp2 { get; set; }
+
+    public readonly bool Equals(Matrix3X3 obj)
+    {
+        return ScaleX == (double)obj.ScaleX && SkewX == (double)obj.SkewX &&
+               TransX == (double)obj.TransX && SkewY == (double)obj.SkewY &&
+               ScaleY == (double)obj.ScaleY && TransY == (double)obj.TransY &&
+               Persp0 == (double)obj.Persp0 && Persp1 == (double)obj.Persp1 &&
+               Persp2 == (double)obj.Persp2;
+    }
+
+    public override readonly bool Equals(object obj)
+    {
+        return obj is Matrix3X3 Matrix3x3 && Equals(Matrix3x3);
+    }
+
+    public static bool operator ==(Matrix3X3 left, Matrix3X3 right)
+    {
+        return left.Equals(right);
+    }
+
+    public static bool operator !=(Matrix3X3 left, Matrix3X3 right)
+    {
+        return !left.Equals(right);
+    }
+
+    public override readonly int GetHashCode()
+    {
+        var hashCode = new HashCode();
+        hashCode.Add(ScaleX);
+        hashCode.Add(SkewX);
+        hashCode.Add(TransX);
+        hashCode.Add(SkewY);
+        hashCode.Add(ScaleY);
+        hashCode.Add(TransY);
+        hashCode.Add(Persp0);
+        hashCode.Add(Persp1);
+        hashCode.Add(Persp2);
+        return hashCode.ToHashCode();
+    }
+
+    public Matrix3X3(float[] values)
+    {
+        if (values == null)
+        {
+            throw new ArgumentNullException(nameof(values));
+        }
+
+        ScaleX = values.Length == 9
+            ? values[0]
+            : throw new ArgumentException(string.Format("The matrix array must have a length of {0}.", 9),
+                nameof(values));
+        SkewX = values[1];
+        TransX = values[2];
+        SkewY = values[3];
+        ScaleY = values[4];
+        TransY = values[5];
+        Persp0 = values[6];
+        Persp1 = values[7];
+        Persp2 = values[8];
+    }
+
+    public Matrix3X3(
+        float scaleX,
+        float skewX,
+        float transX,
+        float skewY,
+        float scaleY,
+        float transY,
+        float persp0,
+        float persp1,
+        float persp2)
+    {
+        ScaleX = scaleX;
+        SkewX = skewX;
+        TransX = transX;
+        SkewY = skewY;
+        ScaleY = scaleY;
+        TransY = transY;
+        Persp0 = persp0;
+        Persp1 = persp1;
+        Persp2 = persp2;
+    }
+    
+    public readonly bool IsIdentity => Equals(Identity);
+
+    /// <summary>
+    ///     Gets or sets the matrix as a flat array: [ScaleX, SkewX, TransX, SkewY, ScaleY, TransY, Persp0, Persp1,
+    ///     Persp2].
+    /// </summary>
+    /// <value />
+    public float[] Values
+    {
+        readonly get => new float[9] {ScaleX, SkewX, TransX, SkewY, ScaleY, TransY, Persp0, Persp1, Persp2};
+        set
+        {
+            if (value == null)
+            {
+                throw new ArgumentNullException(nameof(Values));
+            }
+
+            ScaleX = value.Length == 9
+                ? value[0]
+                : throw new ArgumentException(string.Format("The matrix array must have a length of {0}.", 9),
+                    nameof(Values));
+            SkewX = value[1];
+            TransX = value[2];
+            SkewY = value[3];
+            ScaleY = value[4];
+            TransY = value[5];
+            Persp0 = value[6];
+            Persp1 = value[7];
+            Persp2 = value[8];
+        }
+    }
+
+    /// <param name="values">The array to populate.</param>
+    /// <summary>Populates the specified array with the matrix values.</summary>
+    /// <remarks>The result will be the same as <see cref="P:SkiaSharp.Matrix3x3.Values" />.</remarks>
+    public readonly void GetValues(float[] values)
+    {
+        if (values == null)
+        {
+            throw new ArgumentNullException(nameof(values));
+        }
+
+        if (values.Length != 9)
+        {
+            throw new ArgumentException(string.Format("The matrix array must have a length of {0}.", 9),
+                nameof(values));
+        }
+
+        values[0] = ScaleX;
+        values[1] = SkewX;
+        values[2] = TransX;
+        values[3] = SkewY;
+        values[4] = ScaleY;
+        values[5] = TransY;
+        values[6] = Persp0;
+        values[7] = Persp1;
+        values[8] = Persp2;
+    }
+    
+    public VecD MapPoint(int p0, int p1)
+    {
+        return DrawingBackendApi.Current.MatrixImplementation.MapPoint(this, p0, p1);   
+    }
+    
+    public VecD MapPoint(VecI size) => MapPoint(size.X, size.Y);
+
+    public static Matrix3X3 CreateIdentity()
+    {
+        return new Matrix3X3() { ScaleX = 1f, ScaleY = 1f, Persp2 = 1f };
+    }
+
+    public static Matrix3X3 CreateTranslation(float x, float y)
+    {
+        if (x == 0.0 && y == 0.0)
+        {
+            return Identity;
+        }
+
+        return new Matrix3X3
+        {
+            ScaleX = 1f,
+            ScaleY = 1f,
+            TransX = x,
+            TransY = y,
+            Persp2 = 1f
+        };
+    }
+    
+    public static Matrix3X3 CreateScale(float x, float y)
+    {
+        if (x == 1.0 && y == 1.0)
+        {
+            return Identity;
+        }
+
+        return new Matrix3X3 {ScaleX = x, ScaleY = y, Persp2 = 1f};
+    }
+    
+    public static Matrix3X3 CreateScale(float x, float y, float pivotX, float pivotY)
+    {
+        if (x == 1.0 && y == 1.0)
+        {
+            return Identity;
+        }
+
+        var num1 = pivotX - (x * pivotX);
+        var num2 = pivotY - (y * pivotY);
+        return new Matrix3X3
+        {
+            ScaleX = x,
+            ScaleY = y,
+            TransX = num1,
+            TransY = num2,
+            Persp2 = 1f
+        };
+    }
+    
+    public static Matrix3X3 CreateRotation(float radians)
+    {
+        if (radians == 0.0)
+        {
+            return Identity;
+        }
+
+        var sin = (float)Math.Sin(radians);
+        var cos = (float)Math.Cos(radians);
+        var identity = Identity;
+        SetSinCos(ref identity, sin, cos);
+        return identity;
+    }
+    
+    public static Matrix3X3 CreateRotation(float radians, float pivotX, float pivotY)
+    {
+        if (radians == 0.0)
+        {
+            return Identity;
+        }
+
+        var sin = (float)Math.Sin(radians);
+        var cos = (float)Math.Cos(radians);
+        var identity = Identity;
+        SetSinCos(ref identity, sin, cos, pivotX, pivotY);
+        return identity;
+    }
+
+
+    public static Matrix3X3 CreateRotationDegrees(float degrees)
+    {
+        return degrees == 0.0 ? Identity : CreateRotation(degrees * ((float)Math.PI / 180f));
+    }
+    
+    public static Matrix3X3 CreateRotationDegrees(float degrees, float pivotX, float pivotY)
+    {
+        return degrees == 0.0 ? Identity : CreateRotation(degrees * ((float)Math.PI / 180f), pivotX, pivotY);
+    }
+    
+    public static Matrix3X3 CreateSkew(float x, float y)
+    {
+        if (x == 0.0 && y == 0.0)
+        {
+            return Identity;
+        }
+
+        return new Matrix3X3
+        {
+            ScaleX = 1f,
+            SkewX = x,
+            SkewY = y,
+            ScaleY = 1f,
+            Persp2 = 1f
+        };
+    }
+
+    public static Matrix3X3 CreateScaleTranslation(float sx, float sy, float tx, float ty)
+    {
+        if (sx == 0.0 && sy == 0.0 && tx == 0.0 && ty == 0.0)
+        {
+            return Identity;
+        }
+
+        return new Matrix3X3
+        {
+            ScaleX = sx,
+            SkewX = 0.0f,
+            TransX = tx,
+            SkewY = 0.0f,
+            ScaleY = sy,
+            TransY = ty,
+            Persp0 = 0.0f,
+            Persp1 = 0.0f,
+            Persp2 = 1f
+        };
+    }
+
+    public readonly bool IsInvertible => TryInvert(out _);
+
+    /// <param name="inverse">The destination value to store the inverted matrix if the matrix can be inverted.</param>
+    /// <summary>Attempts to invert the matrix, if possible the inverse matrix contains the result.</summary>
+    /// <returns>
+    ///     True if the matrix can be inverted, and the inverse parameter is initialized with the inverted matrix, false
+    ///     otherwise.
+    /// </returns>
+    public readonly bool TryInvert(out Matrix3X3 inverse)
+    {
+        return DrawingBackendApi.Current.MatrixImplementation.TryInvert(this, out inverse);
+    }
+    
+    public readonly Matrix3X3 Invert() => TryInvert(out var inverse) ? inverse : Matrix3X3.Empty;
+
+    public static Matrix3X3 Concat(Matrix3X3 first, Matrix3X3 second)
+    {
+        return DrawingBackendApi.Current.MatrixImplementation.Concat(in first, in second);
+    }
+    
+    public Matrix3X3 PostConcat(Matrix3X3 globalMatrix)
+    {
+        return DrawingBackendApi.Current.MatrixImplementation.PostConcat(in this, in globalMatrix);
+    }
+
+    private static void SetSinCos(ref Matrix3X3 matrix, float sin, float cos)
+    {
+        matrix.ScaleX = cos;
+        matrix.SkewX = -sin;
+        matrix.TransX = 0.0f;
+        matrix.SkewY = sin;
+        matrix.ScaleY = cos;
+        matrix.TransY = 0.0f;
+        matrix.Persp0 = 0.0f;
+        matrix.Persp1 = 0.0f;
+        matrix.Persp2 = 1f;
+    }
+
+    private static void SetSinCos(
+        ref Matrix3X3 matrix,
+        float sin,
+        float cos,
+        float pivotx,
+        float pivoty)
+    {
+        var c = 1f - cos;
+        matrix.ScaleX = cos;
+        matrix.SkewX = -sin;
+        matrix.TransX = Dot(sin, pivoty, c, pivotx);
+        matrix.SkewY = sin;
+        matrix.ScaleY = cos;
+        matrix.TransY = Dot(-sin, pivotx, c, pivoty);
+        matrix.Persp0 = 0.0f;
+        matrix.Persp1 = 0.0f;
+        matrix.Persp2 = 1f;
+    }
+
+    private static float Dot(float a, float b, float c, float d)
+    {
+        return (float)((a * (double)b) + (c * (double)d));
+    }
+
+    private static float Cross(float a, float b, float c, float d)
+    {
+        return (float)((a * (double)b) - (c * (double)d));
+    }
+    
+
+    private class Indices
+    {
+        public const int ScaleX = 0;
+        public const int SkewX = 1;
+        public const int TransX = 2;
+        public const int SkewY = 3;
+        public const int ScaleY = 4;
+        public const int TransY = 5;
+        public const int Persp0 = 6;
+        public const int Persp1 = 7;
+        public const int Persp2 = 8;
+        public const int Count = 9;
+    }
+}

+ 9 - 24
src/ChunkyImageLib/DataHolders/RectD.cs → src/PixiEditor.DrawingApi.Core/Numerics/RectD.cs

@@ -1,6 +1,6 @@
-using SkiaSharp;
+using System;
 
-namespace ChunkyImageLib.DataHolders;
+namespace PixiEditor.DrawingApi.Core.Numerics;
 public struct RectD : IEquatable<RectD>
 {
     public static RectD Empty { get; } = new RectD();
@@ -106,6 +106,12 @@ public struct RectD : IEquatable<RectD>
         right = x + width;
         bottom = y + height;
     }
+    
+    public static RectD Create(VecI pos, VecI size)
+    {
+        return new RectD(pos.X, pos.Y, size.X, size.Y);
+    }
+    
     public static RectD FromSides(double left, double right, double top, double bottom)
     {
         return new RectD()
@@ -316,28 +322,7 @@ public struct RectD : IEquatable<RectD>
         };
     }
 
-    public static explicit operator SKRect(RectD rect)
-    {
-        return new SKRect((float)rect.left, (float)rect.top, (float)rect.right, (float)rect.bottom);
-    }
-
-    public static explicit operator SKRectI(RectD rect)
-    {
-        return new SKRectI((int)rect.left, (int)rect.top, (int)rect.right, (int)rect.bottom);
-    }
-
-    public static implicit operator RectD(SKRect rect)
-    {
-        return new RectD()
-        {
-            Left = rect.Left,
-            Right = rect.Right,
-            Top = rect.Top,
-            Bottom = rect.Bottom
-        };
-    }
-
-    public static implicit operator RectD(SKRectI rect)
+    public static explicit operator RectD(RectI rect)
     {
         return new RectD()
         {

+ 28 - 46
src/ChunkyImageLib/DataHolders/RectI.cs → src/PixiEditor.DrawingApi.Core/Numerics/RectI.cs

@@ -1,6 +1,6 @@
-using SkiaSharp;
+using System;
 
-namespace ChunkyImageLib.DataHolders;
+namespace PixiEditor.DrawingApi.Core.Numerics;
 public struct RectI : IEquatable<RectI>
 {
     public static RectI Empty { get; } = new RectI();
@@ -196,7 +196,10 @@ public struct RectI : IEquatable<RectI>
     /// </summary>
     public readonly RectI AspectFit(RectI rect)
     {
-        return (RectI)((RectD)this).AspectFit(rect);
+        RectD rectD = (RectD)rect;
+        RectD thisD = (RectD)this;
+        RectD aspect = rectD.AspectFit(thisD);
+        return new RectI((int)aspect.Left, (int)aspect.Top, (int)aspect.Width, (int)aspect.Height);
     }
 
     public readonly bool ContainsInclusive(VecI point) => ContainsInclusive(point.X, point.Y);
@@ -271,49 +274,6 @@ public struct RectI : IEquatable<RectI>
         };
     }
 
-    public static implicit operator RectD(RectI rect)
-    {
-        return new RectD()
-        {
-            Left = rect.left,
-            Right = rect.right,
-            Top = rect.top,
-            Bottom = rect.bottom
-        };
-    }
-
-    public static implicit operator SKRect(RectI rect)
-    {
-        return new SKRect(rect.left, rect.top, rect.right, rect.bottom);
-    }
-
-    public static implicit operator SKRectI(RectI rect)
-    {
-        return new SKRectI(rect.left, rect.top, rect.right, rect.bottom);
-    }
-
-    public static explicit operator RectI(SKRect rect)
-    {
-        return new RectI()
-        {
-            Left = (int)rect.Left,
-            Right = (int)rect.Right,
-            Top = (int)rect.Top,
-            Bottom = (int)rect.Bottom
-        };
-    }
-
-    public static implicit operator RectI(SKRectI rect)
-    {
-        return new RectI()
-        {
-            Left = rect.Left,
-            Right = rect.Right,
-            Top = rect.Top,
-            Bottom = rect.Bottom
-        };
-    }
-
     public static bool operator ==(RectI left, RectI right)
     {
         return left.left == right.left && left.right == right.right && left.top == right.top && left.bottom == right.bottom;
@@ -343,4 +303,26 @@ public struct RectI : IEquatable<RectI>
     {
         return $"{{X: {X}, Y: {Y}, W: {Width}, H: {Height}}}";
     }
+
+    public static RectI Create(int width, int height)
+    {
+        return new RectI()
+        {
+            Left = 0,
+            Right = width,
+            Top = 0,
+            Bottom = height
+        };
+    }
+    
+    public static RectI Create(int x, int y, int width, int height)
+    {
+        return new RectI()
+        {
+            Left = x,
+            Right = width,
+            Top = y,
+            Bottom = height
+        };
+    }
 }

Some files were not shown because too many files changed in this diff