Browse Source

Initial structure

flabbet 2 years ago
parent
commit
44b7e2a80b
51 changed files with 1101 additions and 131 deletions
  1. 9 4
      src/ChunkyImageLib/Chunk.cs
  2. 26 26
      src/ChunkyImageLib/ChunkyImage.cs
  3. 4 1
      src/ChunkyImageLib/ChunkyImageLib.csproj
  4. 1 1
      src/ChunkyImageLib/CommittedChunkStorage.cs
  5. 9 13
      src/ChunkyImageLib/DataHolders/ColorBounds.cs
  6. 1 1
      src/ChunkyImageLib/DataHolders/VecI.cs
  7. 1 1
      src/ChunkyImageLib/Operations/BresenhamLineOperation.cs
  8. 9 9
      src/ChunkyImageLib/Operations/ChunkyImageOperation.cs
  9. 4 4
      src/ChunkyImageLib/Operations/ClearPathOperation.cs
  10. 4 4
      src/ChunkyImageLib/Operations/ClearRegionOperation.cs
  11. 1 1
      src/ChunkyImageLib/Operations/EllipseOperation.cs
  12. 4 4
      src/ChunkyImageLib/Operations/ImageOperation.cs
  13. 1 1
      src/ChunkyImageLib/Operations/PathOperation.cs
  14. 1 1
      src/ChunkyImageLib/Operations/PixelOperation.cs
  15. 1 1
      src/ChunkyImageLib/Operations/PixelsOperation.cs
  16. 2 2
      src/ChunkyImageLib/Operations/RectangleOperation.cs
  17. 1 1
      src/ChunkyImageLib/Operations/ReplaceColorOperation.cs
  18. 1 1
      src/ChunkyImageLib/Operations/SkiaLineOperation.cs
  19. 19 16
      src/ChunkyImageLib/Surface.cs
  20. 1 1
      src/ChunkyImageLibTest/ChunkyImageTests.cs
  21. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillChunkCache.cs
  22. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs
  23. 4 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelectedArea_UpdateableChange.cs
  24. 5 5
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs
  25. 13 13
      src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs
  26. 32 0
      src/PixiEditor.DrawingApi.Core/Bridge/DrawingBackendApi.cs
  27. 13 0
      src/PixiEditor.DrawingApi.Core/Bridge/IDrawingBackend.cs
  28. 11 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/ICanvasOperations.cs
  29. 10 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/IDrawingBackendColorOperations.cs
  30. 11 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/IImageOperations.cs
  31. 9 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/IPaintOperations.cs
  32. 212 0
      src/PixiEditor.DrawingApi.Core/ColorsImpl/Color.cs
  33. 400 0
      src/PixiEditor.DrawingApi.Core/ColorsImpl/ColorF.cs
  34. 15 0
      src/PixiEditor.DrawingApi.Core/Exceptions/InitializationDuplicateException.cs
  35. 13 0
      src/PixiEditor.DrawingApi.Core/PixiEditor.DrawingApi.Core.csproj
  36. 92 0
      src/PixiEditor.DrawingApi.Core/Surface/BlendMode.cs
  37. 14 0
      src/PixiEditor.DrawingApi.Core/Surface/Canvas.cs
  38. 18 0
      src/PixiEditor.DrawingApi.Core/Surface/DrawingSurface.cs
  39. 7 0
      src/PixiEditor.DrawingApi.Core/Surface/DrawingSurfaceProperties.cs
  40. 18 0
      src/PixiEditor.DrawingApi.Core/Surface/FilterQuality.cs
  41. 25 0
      src/PixiEditor.DrawingApi.Core/Surface/Image.cs
  42. 22 0
      src/PixiEditor.DrawingApi.Core/Surface/Paint.cs
  43. 9 0
      src/PixiEditor.DrawingApi.Core/Surface/PixelDataObject.cs
  44. 32 0
      src/PixiEditor.sln
  45. 1 1
      src/PixiEditor/Helpers/DocumentViewModelBuilder.cs
  46. 1 1
      src/PixiEditor/Helpers/SurfaceHelpers.cs
  47. 1 1
      src/PixiEditor/Models/Controllers/ClipboardController.cs
  48. 1 1
      src/PixiEditor/Models/IO/Exporter.cs
  49. 1 1
      src/PixiEditor/Models/IO/Importer.cs
  50. 2 2
      src/PixiEditor/Models/Rendering/WriteableBitmapUpdater.cs
  51. 4 4
      src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

+ 9 - 4
src/ChunkyImageLib/Chunk.cs

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

+ 26 - 26
src/ChunkyImageLib/ChunkyImage.cs

@@ -218,8 +218,8 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 using Chunk tempChunk = Chunk.Create(ChunkResolution.Eighth);
                 using Chunk tempChunk = Chunk.Create(ChunkResolution.Eighth);
                 using SKPaint committedPaint = new SKPaint() { Color = committedColor, BlendMode = SKBlendMode.Src };
                 using SKPaint committedPaint = new SKPaint() { Color = committedColor, BlendMode = SKBlendMode.Src };
                 using SKPaint latestPaint = new SKPaint() { Color = latestColor, BlendMode = this.blendMode };
                 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);
+                tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, committedPaint);
+                tempChunk.Surface.DrawingSurface.Canvas.DrawPixel(VecI.Zero, latestPaint);
                 return tempChunk.Surface.GetSRGBPixel(VecI.Zero);
                 return tempChunk.Surface.GetSRGBPixel(VecI.Zero);
             }
             }
         }
         }
@@ -272,11 +272,11 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
 
             // combine with committed and then draw
             // combine with committed and then draw
             using var tempChunk = Chunk.Create(resolution);
             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;
             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)
             if (lockTransparency)
-                OperationHelper.ClampAlpha(tempChunk.Surface.SkiaSurface, committedChunk.Surface.SkiaSurface);
+                OperationHelper.ClampAlpha(tempChunk.Surface.DrawingSurface, committedChunk.Surface.DrawingSurface);
             tempChunk.DrawOnSurface(surface, pos, paint);
             tempChunk.DrawOnSurface(surface, pos, paint);
 
 
             return true;
             return true;
@@ -786,13 +786,13 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                     if (lockTransparency)
                     if (lockTransparency)
                     {
                     {
                         using Chunk tempChunk = Chunk.Create(resolution);
                         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
                     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();
                     chunk.Dispose();
@@ -912,7 +912,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             if (lockTransparency && !chunkData.IsDeleted && MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is not null)
             if (lockTransparency && !chunkData.IsDeleted && MaybeGetCommittedChunk(chunkPos, ChunkResolution.Full) is not null)
             {
             {
                 var committed = GetCommittedChunk(chunkPos, resolution);
                 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;
             chunkData.QueueProgress = queuedOperations.Count;
@@ -936,13 +936,13 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         }
         }
 
 
         var intersection = Chunk.Create(resolution);
         var intersection = Chunk.Create(resolution);
-        intersection.Surface.SkiaSurface.Canvas.Clear(SKColors.White);
+        intersection.Surface.DrawingSurface.Canvas.Clear(SKColors.White);
 
 
         foreach (var mask in activeClips)
         foreach (var mask in activeClips)
         {
         {
             if (mask.CommittedChunkExists(chunkPos))
             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
             else
             {
             {
@@ -974,7 +974,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
                 return chunkData.IsDeleted;
                 return chunkData.IsDeleted;
 
 
             if (chunkData.IsDeleted)
             if (chunkData.IsDeleted)
-                targetChunk.Surface.SkiaSurface.Canvas.Clear();
+                targetChunk.Surface.DrawingSurface.Canvas.Clear();
 
 
             // just regular drawing
             // just regular drawing
             if (combinedRasterClips.IsT0) //Everything is visible as far as raster clips are concerned
             if (combinedRasterClips.IsT0) //Everything is visible as far as raster clips are concerned
@@ -987,14 +987,14 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             var clip = combinedRasterClips.AsT2;
             var clip = combinedRasterClips.AsT2;
 
 
             using var tempChunk = Chunk.Create(targetChunk.Resolution);
             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);
             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;
             return false;
         }
         }
 
 
@@ -1010,15 +1010,15 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     {
     {
         if (clippingPath is not null && !clippingPath.IsEmpty)
         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 SKPath transformedPath = new(clippingPath);
             float scale = (float)resolution.Multiplier();
             float scale = (float)resolution.Multiplier();
             VecD trans = -chunkPos * FullChunkSize * scale;
             VecD trans = -chunkPos * FullChunkSize * scale;
             transformedPath.Transform(SKMatrix.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
             transformedPath.Transform(SKMatrix.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
-            targetChunk.Surface.SkiaSurface.Canvas.ClipPath(transformedPath);
+            targetChunk.Surface.DrawingSurface.Canvas.ClipPath(transformedPath);
             operation.DrawOnChunk(targetChunk, chunkPos);
             operation.DrawOnChunk(targetChunk, chunkPos);
-            targetChunk.Surface.SkiaSurface.Canvas.RestoreToCount(count);
+            targetChunk.Surface.DrawingSurface.Canvas.RestoreToCount(count);
         }
         }
         else
         else
         {
         {
@@ -1101,11 +1101,11 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
         if (existingFullResChunk is not null)
         if (existingFullResChunk is not null)
         {
         {
             var newChunk = Chunk.Create(resolution);
             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;
             committedChunks[resolution][chunkPos] = newChunk;
             return newChunk;
             return newChunk;
         }
         }
@@ -1137,7 +1137,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
             if (blendMode == SKBlendMode.Src)
             if (blendMode == SKBlendMode.Src)
                 maybeCommittedAnyRes.Surface.CopyTo(newChunk.Surface);
                 maybeCommittedAnyRes.Surface.CopyTo(newChunk.Surface);
             else
             else
-                newChunk.Surface.SkiaSurface.Canvas.Clear();
+                newChunk.Surface.DrawingSurface.Canvas.Clear();
             latestChunks[resolution][chunkPos] = newChunk;
             latestChunks[resolution][chunkPos] = newChunk;
             return newChunk;
             return newChunk;
         }
         }
@@ -1157,7 +1157,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
 
 
         // no previous chunks exist
         // no previous chunks exist
         var newLatestChunk = Chunk.Create(resolution);
         var newLatestChunk = Chunk.Create(resolution);
-        newLatestChunk.Surface.SkiaSurface.Canvas.Clear();
+        newLatestChunk.Surface.DrawingSurface.Canvas.Clear();
         latestChunks[resolution][chunkPos] = newLatestChunk;
         latestChunks[resolution][chunkPos] = newLatestChunk;
         return newLatestChunk;
         return newLatestChunk;
     }
     }

+ 4 - 1
src/ChunkyImageLib/ChunkyImageLib.csproj

@@ -12,7 +12,10 @@
     <PackageReference Include="ComputeSharp.Core" Version="2.0.0-alpha.27" />
     <PackageReference Include="ComputeSharp.Core" Version="2.0.0-alpha.27" />
     <PackageReference Include="ComputeSharp.Dynamic" Version="2.0.0-alpha.27" />
     <PackageReference Include="ComputeSharp.Dynamic" Version="2.0.0-alpha.27" />
     <PackageReference Include="OneOf" Version="3.0.216" />
     <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>
   </ItemGroup>
 
 
 </Project>
 </Project>

+ 1 - 1
src/ChunkyImageLib/CommittedChunkStorage.cs

@@ -14,7 +14,7 @@ public class CommittedChunkStorage : IDisposable
         foreach (var chunkPos in committedChunksToSave)
         foreach (var chunkPos in committedChunksToSave)
         {
         {
             Chunk copy = Chunk.Create();
             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();
                 copy.Dispose();
                 savedChunks.Add((chunkPos, null));
                 savedChunks.Add((chunkPos, null));

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

@@ -1,11 +1,7 @@
-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 ComputeSharp;
-using SkiaSharp;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.PixelE;
 
 
 namespace ChunkyImageLib.DataHolders;
 namespace ChunkyImageLib.DataHolders;
 
 
@@ -20,7 +16,7 @@ public struct ColorBounds
     public float UpperB { get; set; }
     public float UpperB { get; set; }
     public float UpperA { 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)
         static (float lower, float upper) FindInclusiveBoundaryPremul(byte channel, float alpha)
         {
         {
@@ -36,12 +32,12 @@ public struct ColorBounds
             return (subHalf / 255f, addHalf / 255f);
             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)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]

+ 1 - 1
src/ChunkyImageLib/DataHolders/VecI.cs

@@ -1,4 +1,4 @@
-using SkiaSharp;
+using PixiEditor.PixelE;
 
 
 namespace ChunkyImageLib.DataHolders;
 namespace ChunkyImageLib.DataHolders;
 
 

+ 1 - 1
src/ChunkyImageLib/Operations/BresenhamLineOperation.cs

@@ -27,7 +27,7 @@ internal class BresenhamLineOperation : IDrawOperation
         // a hacky way to make the lines look slightly better on non full res chunks
         // a hacky way to make the lines look slightly better on non full res chunks
         paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
         paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
 
 
-        var surf = chunk.Surface.SkiaSurface;
+        var surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);

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

@@ -21,7 +21,7 @@ internal class ChunkyImageOperation : IDrawOperation
 
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
     {
-        chunk.Surface.SkiaSurface.Canvas.Save();
+        chunk.Surface.DrawingSurface.Canvas.Save();
 
 
         {
         {
             VecI pixelPos = chunkPos * ChunkyImage.FullChunkSize;
             VecI pixelPos = chunkPos * ChunkyImage.FullChunkSize;
@@ -29,7 +29,7 @@ internal class ChunkyImageOperation : IDrawOperation
             SKRect clippingRect = SKRect.Create(
             SKRect clippingRect = SKRect.Create(
                 OperationHelper.ConvertForResolution(topLeftImageCorner - pixelPos, chunk.Resolution),
                 OperationHelper.ConvertForResolution(topLeftImageCorner - pixelPos, chunk.Resolution),
                 OperationHelper.ConvertForResolution(imageToDraw.CommittedSize, 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;
         VecI chunkPixelCenter = chunkPos * ChunkyImage.FullChunkSize;
@@ -40,12 +40,12 @@ internal class ChunkyImageOperation : IDrawOperation
         VecI chunkSize = chunk.PixelSize;
         VecI chunkSize = chunk.PixelSize;
         if (mirrorHorizontal)
         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;
             chunkCenterOnImage.X = -chunkCenterOnImage.X;
         }
         }
         if (mirrorVertical)
         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;
             chunkCenterOnImage.Y = -chunkCenterOnImage.Y;
         }
         }
 
 
@@ -61,7 +61,7 @@ internal class ChunkyImageOperation : IDrawOperation
         imageToDraw.DrawCommittedChunkOn(
         imageToDraw.DrawCommittedChunkOn(
             topLeft,
             topLeft,
             chunk.Resolution,
             chunk.Resolution,
-            chunk.Surface.SkiaSurface,
+            chunk.Surface.DrawingSurface,
             (VecI)((topLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
             (VecI)((topLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
 
 
         VecI gridShift = pos % ChunkyImage.FullChunkSize;
         VecI gridShift = pos % ChunkyImage.FullChunkSize;
@@ -70,7 +70,7 @@ internal class ChunkyImageOperation : IDrawOperation
             imageToDraw.DrawCommittedChunkOn(
             imageToDraw.DrawCommittedChunkOn(
             topRight,
             topRight,
             chunk.Resolution,
             chunk.Resolution,
-            chunk.Surface.SkiaSurface,
+            chunk.Surface.DrawingSurface,
             (VecI)((topRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
             (VecI)((topRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
         }
         }
         if (gridShift.Y != 0)
         if (gridShift.Y != 0)
@@ -78,7 +78,7 @@ internal class ChunkyImageOperation : IDrawOperation
             imageToDraw.DrawCommittedChunkOn(
             imageToDraw.DrawCommittedChunkOn(
             bottomLeft,
             bottomLeft,
             chunk.Resolution,
             chunk.Resolution,
-            chunk.Surface.SkiaSurface,
+            chunk.Surface.DrawingSurface,
             (VecI)((bottomLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
             (VecI)((bottomLeft * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
         }
         }
         if (gridShift.X != 0 && gridShift.Y != 0)
         if (gridShift.X != 0 && gridShift.Y != 0)
@@ -86,11 +86,11 @@ internal class ChunkyImageOperation : IDrawOperation
             imageToDraw.DrawCommittedChunkOn(
             imageToDraw.DrawCommittedChunkOn(
             bottomRight,
             bottomRight,
             chunk.Resolution,
             chunk.Resolution,
-            chunk.Surface.SkiaSurface,
+            chunk.Surface.DrawingSurface,
             (VecI)((bottomRight * ChunkyImage.FullChunkSize - chunkCenterOnImage).Add(ChunkyImage.FullChunkSize / 2) * chunk.Resolution.Multiplier()));
             (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)
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)

+ 4 - 4
src/ChunkyImageLib/Operations/ClearPathOperation.cs

@@ -17,15 +17,15 @@ internal class ClearPathOperation : IDrawOperation
 
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
     {
-        chunk.Surface.SkiaSurface.Canvas.Save();
+        chunk.Surface.DrawingSurface.Canvas.Save();
 
 
         using SKPath transformedPath = new(path);
         using SKPath transformedPath = new(path);
         float scale = (float)chunk.Resolution.Multiplier();
         float scale = (float)chunk.Resolution.Multiplier();
         VecD trans = -chunkPos * ChunkyImage.FullChunkSize * scale;
         VecD trans = -chunkPos * ChunkyImage.FullChunkSize * scale;
         transformedPath.Transform(SKMatrix.CreateScaleTranslation(scale, scale, (float)trans.X, (float)trans.Y));
         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();
+        chunk.Surface.DrawingSurface.Canvas.ClipPath(transformedPath);
+        chunk.Surface.DrawingSurface.Canvas.Clear();
+        chunk.Surface.DrawingSurface.Canvas.Restore();
     }
     }
 
 
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)

+ 4 - 4
src/ChunkyImageLib/Operations/ClearRegionOperation.cs

@@ -19,10 +19,10 @@ internal class ClearRegionOperation : IDrawOperation
         VecI convPos = OperationHelper.ConvertForResolution(rect.Pos, chunk.Resolution);
         VecI convPos = OperationHelper.ConvertForResolution(rect.Pos, chunk.Resolution);
         VecI convSize = OperationHelper.ConvertForResolution(rect.Size, 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(SKRect.Create(convPos - chunkPos.Multiply(chunk.PixelSize), convSize));
+        chunk.Surface.DrawingSurface.Canvas.Clear();
+        chunk.Surface.DrawingSurface.Canvas.Restore();
     }
     }
 
 
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)

+ 1 - 1
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -53,7 +53,7 @@ internal class EllipseOperation : IDrawOperation
     {
     {
         if (!init)
         if (!init)
             Init();
             Init();
-        var surf = chunk.Surface.SkiaSurface;
+        var surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);

+ 4 - 4
src/ChunkyImageLib/Operations/ImageOperation.cs

@@ -86,10 +86,10 @@ internal class ImageOperation : IDrawOperation
         var scaleTrans = SKMatrix.CreateScaleTranslation(scaleMult, scaleMult, (float)trans.X * scaleMult, (float)trans.Y * scaleMult);
         var scaleTrans = SKMatrix.CreateScaleTranslation(scaleMult, scaleMult, (float)trans.X * scaleMult, (float)trans.Y * scaleMult);
         var finalMatrix = SKMatrix.Concat(scaleTrans, transformMatrix);
         var finalMatrix = SKMatrix.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)
     public HashSet<VecI> FindAffectedChunks(VecI imageSize)

+ 1 - 1
src/ChunkyImageLib/Operations/PathOperation.cs

@@ -23,7 +23,7 @@ internal class PathOperation : IDrawOperation
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
     {
         paint.IsAntialias = chunk.Resolution != ChunkResolution.Full;
         paint.IsAntialias = chunk.Resolution != ChunkResolution.Full;
-        var surf = chunk.Surface.SkiaSurface;
+        var surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);

+ 1 - 1
src/ChunkyImageLib/Operations/PixelOperation.cs

@@ -24,7 +24,7 @@ internal class PixelOperation : IDrawOperation
         // a hacky way to make the lines look slightly better on non full res chunks
         // a hacky way to make the lines look slightly better on non full res chunks
         paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
         paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
 
 
-        SKSurface surf = chunk.Surface.SkiaSurface;
+        SKSurface surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);

+ 1 - 1
src/ChunkyImageLib/Operations/PixelsOperation.cs

@@ -24,7 +24,7 @@ internal class PixelsOperation : IDrawOperation
         // a hacky way to make the lines look slightly better on non full res chunks
         // a hacky way to make the lines look slightly better on non full res chunks
         paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
         paint.Color = new SKColor(color.Red, color.Green, color.Blue, (byte)(color.Alpha * chunk.Resolution.Multiplier()));
 
 
-        SKSurface surf = chunk.Surface.SkiaSurface;
+        SKSurface surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);

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

@@ -16,9 +16,9 @@ internal class RectangleOperation : IDrawOperation
 
 
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     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 rect = RectD.FromCenterAndSize(Data.Center, Data.Size.Abs());
         var innerRect = rect.Inflate(-Data.StrokeWidth);
         var innerRect = rect.Inflate(-Data.StrokeWidth);

+ 1 - 1
src/ChunkyImageLib/Operations/ReplaceColorOperation.cs

@@ -38,7 +38,7 @@ internal class ReplaceColorOperation : IDrawOperation
 
 
     private static void ReplaceColor(HlslColorBounds oldColorBounds, SKColor newColor, Chunk chunk)
     private static void ReplaceColor(HlslColorBounds oldColorBounds, SKColor 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()
         using var texture = GraphicsDevice.GetDefault()
             .AllocateReadWriteTexture2D<uint2>(chunk.PixelSize.X, chunk.PixelSize.Y);
             .AllocateReadWriteTexture2D<uint2>(chunk.PixelSize.X, chunk.PixelSize.Y);
 
 

+ 1 - 1
src/ChunkyImageLib/Operations/SkiaLineOperation.cs

@@ -27,7 +27,7 @@ internal class SkiaLineOperation : IDrawOperation
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     public void DrawOnChunk(Chunk chunk, VecI chunkPos)
     {
     {
         paint.IsAntialias = chunk.Resolution != ChunkResolution.Full;
         paint.IsAntialias = chunk.Resolution != ChunkResolution.Full;
-        var surf = chunk.Surface.SkiaSurface;
+        var surf = chunk.Surface.DrawingSurface;
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Scale((float)chunk.Resolution.Multiplier());
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
         surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);

+ 19 - 16
src/ChunkyImageLib/Surface.cs

@@ -1,6 +1,9 @@
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.PixelE;
 using SkiaSharp;
 using SkiaSharp;
 
 
 namespace ChunkyImageLib;
 namespace ChunkyImageLib;
@@ -9,12 +12,12 @@ public class Surface : IDisposable
 {
 {
     private bool disposed;
     private bool disposed;
     public IntPtr PixelBuffer { get; }
     public IntPtr PixelBuffer { get; }
-    public SKSurface SkiaSurface { get; }
+    public DrawingSurface DrawingSurface { get; }
     public int BytesPerPixel { get; }
     public int BytesPerPixel { get; }
     public VecI Size { 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)
     public Surface(VecI size)
     {
     {
@@ -27,24 +30,24 @@ public class Surface : IDisposable
 
 
         BytesPerPixel = 8;
         BytesPerPixel = 8;
         PixelBuffer = CreateBuffer(size.X, size.Y, BytesPerPixel);
         PixelBuffer = CreateBuffer(size.X, size.Y, BytesPerPixel);
-        SkiaSurface = CreateSKSurface();
+        DrawingSurface = CreateDrawingSurface();
     }
     }
 
 
     public Surface(Surface original) : this(original.Size)
     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)
     public static Surface Load(string path)
     {
     {
         if (!File.Exists(path))
         if (!File.Exists(path))
             throw new FileNotFoundException(null, path);
             throw new FileNotFoundException(null, path);
-        using var image = SKImage.FromEncodedData(path);
+        using var image = Image.FromEncodedData(path);
         if (image is null)
         if (image is null)
             throw new ArgumentException($"The image with path {path} couldn't be loaded");
             throw new ArgumentException($"The image with path {path} couldn't be loaded");
 
 
         var surface = new Surface(new VecI(image.Width, image.Height));
         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;
         return surface;
     }
     }
@@ -57,15 +60,15 @@ public class Surface : IDisposable
         {
         {
             using SKPixmap map = new(info, new IntPtr(pointer));
             using SKPixmap map = new(info, new IntPtr(pointer));
             using SKSurface surface = SKSurface.Create(map);
             using SKSurface surface = SKSurface.Create(map);
-            surface.Draw(SkiaSurface.Canvas, 0, 0, drawingPaint);
+            surface.Draw(DrawingSurface.Canvas, 0, 0, drawingPaint);
         }
         }
     }
     }
 
 
     public Surface ResizeNearestNeighbor(VecI newSize)
     public Surface ResizeNearestNeighbor(VecI newSize)
     {
     {
-        using SKImage image = SkiaSurface.Snapshot();
+        using Image image = DrawingSurface.Snapshot();
         Surface newSurface = new(newSize);
         Surface newSurface = new(newSize);
-        newSurface.SkiaSurface.Canvas.DrawImage(image, new SKRect(0, 0, newSize.X, newSize.Y), nearestNeighborReplacingPaint);
+        newSurface.DrawingSurface.Canvas.DrawImage(image, new SKRect(0, 0, newSize.X, newSize.Y), nearestNeighborReplacingPaint);
         return newSurface;
         return newSurface;
     }
     }
 
 
@@ -74,24 +77,24 @@ public class Surface : IDisposable
         if (other.Size != Size)
         if (other.Size != Size)
             throw new ArgumentException("Target Surface must have the same dimensions");
             throw new ArgumentException("Target Surface must have the same dimensions");
         int bytesC = Size.X * Size.Y * BytesPerPixel;
         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);
         Buffer.MemoryCopy((void*)PixelBuffer, (void*)pixmap.GetPixels(), bytesC, bytesC);
     }
     }
 
 
     /// <summary>
     /// <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.
     /// 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>
     /// </summary>
-    public unsafe SKColor GetSRGBPixel(VecI pos)
+    public unsafe Color GetSRGBPixel(VecI pos)
     {
     {
         Half* ptr = (Half*)(PixelBuffer + (pos.X + pos.Y * Size.X) * BytesPerPixel);
         Half* ptr = (Half*)(PixelBuffer + (pos.X + pos.Y * Size.X) * BytesPerPixel);
         float a = (float)ptr[3];
         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 (SKColor)new SKColorF((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;
         drawingPaint.Color = color;
-        SkiaSurface.Canvas.DrawPoint(pos.X, pos.Y, drawingPaint);
+        DrawingSurface.Canvas.DrawPixel(pos.X, pos.Y, drawingPaint);
     }
     }
 
 
     public unsafe bool IsFullyTransparent()
     public unsafe bool IsFullyTransparent()
@@ -110,7 +113,7 @@ public class Surface : IDisposable
     public void SaveToDesktop(string filename = "savedSurface.png")
     public void SaveToDesktop(string filename = "savedSurface.png")
     {
     {
         using var final = SKSurface.Create(new SKImageInfo(Size.X, Size.Y, SKColorType.Rgba8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb()));
         using var final = SKSurface.Create(new SKImageInfo(Size.X, Size.Y, SKColorType.Rgba8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb()));
-        final.Canvas.DrawSurface(SkiaSurface, 0, 0);
+        final.Canvas.DrawSurface(DrawingSurface, 0, 0);
         using (var snapshot = final.Snapshot())
         using (var snapshot = final.Snapshot())
         {
         {
             using var stream = File.Create(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), filename));
             using var stream = File.Create(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), filename));
@@ -119,7 +122,7 @@ 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 = SKSurface.Create(new SKImageInfo(Size.X, Size.Y, SKColorType.RgbaF16, SKAlphaType.Premul, SKColorSpace.CreateSrgb()), PixelBuffer);
         if (surface is null)
         if (surface is null)

+ 1 - 1
src/ChunkyImageLibTest/ChunkyImageTests.cs

@@ -34,7 +34,7 @@ public class ChunkyImageTests
         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, SKColors.AliceBlue, SKColors.Snow));
         using (Chunk target = Chunk.Create())
         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.CancelChanges();
             image.EnqueueResize(new(ChunkyImage.FullChunkSize * 4, ChunkyImage.FullChunkSize * 4));
             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, SKColors.AliceBlue, SKColors.Snow, SKBlendMode.Multiply));

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

@@ -53,7 +53,7 @@ internal class FloodFillChunkCache : IDisposable
             return new EmptyChunk();
             return new EmptyChunk();
         Chunk chunkOnImage = Chunk.Create(ChunkResolution.Full);
         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();
             chunkOnImage.Dispose();
             acquiredChunks[pos] = new EmptyChunk();
             acquiredChunks[pos] = new EmptyChunk();

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

@@ -66,7 +66,7 @@ internal static class FloodFillHelper
             if (!drawingChunks.ContainsKey(chunkPos))
             if (!drawingChunks.ContainsKey(chunkPos))
             {
             {
                 var chunk = Chunk.Create();
                 var chunk = Chunk.Create();
-                chunk.Surface.SkiaSurface.Canvas.Clear(SKColors.Transparent);
+                chunk.Surface.DrawingSurface.Canvas.Clear(SKColors.Transparent);
                 drawingChunks[chunkPos] = chunk;
                 drawingChunks[chunkPos] = chunk;
             }
             }
             var drawingChunk = drawingChunks[chunkPos];
             var drawingChunk = drawingChunks[chunkPos];
@@ -77,7 +77,7 @@ internal static class FloodFillHelper
             {
             {
                 if (colorToReplace.Alpha == 0 && !processedEmptyChunks.Contains(chunkPos))
                 if (colorToReplace.Alpha == 0 && !processedEmptyChunks.Contains(chunkPos))
                 {
                 {
-                    drawingChunk.Surface.SkiaSurface.Canvas.Clear(drawingColor);
+                    drawingChunk.Surface.DrawingSurface.Canvas.Clear(drawingColor);
                     for (int i = 0; i < chunkSize; i++)
                     for (int i = 0; i < chunkSize; i++)
                     {
                     {
                         if (chunkPos.Y > 0)
                         if (chunkPos.Y > 0)
@@ -143,10 +143,10 @@ internal static class FloodFillHelper
         byte[] pixelStates = new byte[chunkSize * chunkSize];
         byte[] pixelStates = new byte[chunkSize * chunkSize];
         DrawSelection(pixelStates, selection, globalSelectionBounds, chunkPos, 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();
         Half* refArray = (Half*)refPixmap.GetPixels();
 
 
-        using var drawPixmap = drawingChunk.Surface.SkiaSurface.PeekPixels();
+        using var drawPixmap = drawingChunk.Surface.DrawingSurface.PeekPixels();
         Half* drawArray = (Half*)drawPixmap.GetPixels();
         Half* drawArray = (Half*)drawPixmap.GetPixels();
 
 
         Stack<VecI> toVisit = new();
         Stack<VecI> toVisit = new();

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelectedArea_UpdateableChange.cs

@@ -81,10 +81,10 @@ internal class TransformSelectedArea_UpdateableChange : UpdateableChange
 
 
         // draw
         // draw
         Surface output = new(pathBounds.Size);
         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);
         return (output, pathBounds);
     }
     }

+ 5 - 5
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeImage_Change.cs

@@ -49,7 +49,7 @@ internal class ResizeImage_Change : Change
         image.DrawMostUpToDateRegionOn(
         image.DrawMostUpToDateRegionOn(
             new(VecI.Zero, originalSize), 
             new(VecI.Zero, originalSize), 
             ChunkResolution.Full,
             ChunkResolution.Full,
-            originalSurface.SkiaSurface,
+            originalSurface.DrawingSurface,
             VecI.Zero);
             VecI.Zero);
         
         
         bool downscaling = newSize.LengthSquared < originalSize.LengthSquared;
         bool downscaling = newSize.LengthSquared < originalSize.LengthSquared;
@@ -61,10 +61,10 @@ internal class ResizeImage_Change : Change
         };
         };
 
 
         using Surface newSurface = new(newSize);
         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.EnqueueResize(newSize);
         image.EnqueueClear();
         image.EnqueueClear();

+ 13 - 13
src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs

@@ -48,13 +48,13 @@ public static class ChunkRenderer
         context.UpdateFromMember(layer);
         context.UpdateFromMember(layer);
 
 
         Chunk renderingResult = Chunk.Create(resolution);
         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();
             renderingResult.Dispose();
             return new EmptyChunk();
             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
             // should pretty much never happen due to the check above, but you can never be sure with many threads
             renderingResult.Dispose();
             renderingResult.Dispose();
@@ -62,9 +62,9 @@ public static class ChunkRenderer
         }
         }
 
 
         if (clippingChunk.IsT2)
         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;
         return renderingResult;
     }
     }
 
 
@@ -79,15 +79,15 @@ public static class ChunkRenderer
 
 
         context.UpdateFromMember(layer);
         context.UpdateFromMember(layer);
         Chunk renderingResult = Chunk.Create(resolution);
         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();
             renderingResult.Dispose();
             return new EmptyChunk();
             return new EmptyChunk();
         }
         }
 
 
         if (clippingChunk.IsT2)
         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;
         return renderingResult;
     }
     }
 
 
@@ -112,7 +112,7 @@ public static class ChunkRenderer
             return;
             return;
         }
         }
         context.UpdateFromMember(layer);
         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(
     private static OneOf<EmptyChunk, Chunk> RenderFolder(
@@ -140,7 +140,7 @@ public static class ChunkRenderer
 
 
         if (folder.Mask is not null && folder.MaskIsVisible)
         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
                 // this shouldn't really happen due to the check above, but another thread could edit the mask in the meantime
                 contents.Dispose();
                 contents.Dispose();
@@ -149,10 +149,10 @@ public static class ChunkRenderer
         }
         }
 
 
         if (clippingChunk.IsT2)
         if (clippingChunk.IsT2)
-            OperationHelper.ClampAlpha(contents.Surface.SkiaSurface, clippingChunk.AsT2.Surface.SkiaSurface);
+            OperationHelper.ClampAlpha(contents.Surface.DrawingSurface, clippingChunk.AsT2.Surface.DrawingSurface);
         context.UpdateFromMember(folder);
         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;
         return contents;
     }
     }
@@ -168,7 +168,7 @@ public static class ChunkRenderer
             return new EmptyChunk();
             return new EmptyChunk();
 
 
         Chunk targetChunk = Chunk.Create(resolution);
         Chunk targetChunk = Chunk.Create(resolution);
-        targetChunk.Surface.SkiaSurface.Canvas.Clear();
+        targetChunk.Surface.DrawingSurface.Canvas.Clear();
 
 
         OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk = new FilledChunk();
         OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk = new FilledChunk();
         for (int i = 0; i < folder.Children.Count; i++)
         for (int i = 0; i < folder.Children.Count; i++)

+ 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 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 void SetupBackend(IDrawingBackend backend)
+        {
+            if (Current != null)
+            {
+                throw new InitializationDuplicateException("Drawing backend was already initialized.");
+            }
+            
+            _current = backend;
+            backend.Setup();
+        }
+    }
+}

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

@@ -0,0 +1,13 @@
+using PixiEditor.DrawingApi.Core.Bridge.Operations;
+
+namespace PixiEditor.DrawingApi.Core.Bridge
+{
+    public interface IDrawingBackend
+    {
+        public void Setup();
+        public IDrawingBackendColorOperations ColorOperations { get; }
+        public IImageOperations ImageOperations { get; }
+        public ICanvasOperations CanvasOperations { get; }
+        public IPaintOperations PaintOperations { get; set; }
+    }
+}

+ 11 - 0
src/PixiEditor.DrawingApi.Core/Bridge/Operations/ICanvasOperations.cs

@@ -0,0 +1,11 @@
+using PixiEditor.DrawingApi.Core.Surface;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.Operations
+{
+    public interface ICanvasOperations
+    {
+        public void DrawPixel(int posX, int posY, Paint drawingPaint);
+        public void DrawSurface(DrawingSurface drawingSurface, int x, int y);
+        public void DrawImage(Image image, int x, int y);
+    }
+}

+ 10 - 0
src/PixiEditor.DrawingApi.Core/Bridge/Operations/IDrawingBackendColorOperations.cs

@@ -0,0 +1,10 @@
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.Operations
+{
+    public interface IDrawingBackendColorOperations
+    {
+        public ColorF ColorToColorF(uint colorValue);
+        public Color ColorFToColor(ColorF color);
+    }
+}

+ 11 - 0
src/PixiEditor.DrawingApi.Core/Bridge/Operations/IImageOperations.cs

@@ -0,0 +1,11 @@
+using PixiEditor.DrawingApi.Core.Surface;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.Operations
+{
+    public interface IImageOperations
+    {
+        public Image Snapshot(DrawingSurface drawingSurface);
+        public void DisposeImage(Image image);
+        public Image FromEncodedData(string path);
+    }
+}

+ 9 - 0
src/PixiEditor.DrawingApi.Core/Bridge/Operations/IPaintOperations.cs

@@ -0,0 +1,9 @@
+using PixiEditor.DrawingApi.Core.Surface;
+
+namespace PixiEditor.DrawingApi.Core.Bridge.Operations
+{
+    public interface IPaintOperations
+    {
+        public void Dispose(Paint paint);
+    }
+}

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

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

+ 13 - 0
src/PixiEditor.DrawingApi.Core/PixiEditor.DrawingApi.Core.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>netstandard2.1</TargetFramework>
+        <Nullable>enable</Nullable>
+        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+        <LangVersion>10</LangVersion>
+    </PropertyGroup>
+  
+  <ItemGroup>
+    <PackageReference Include="SkiaSharp" Version="2.80.3" />
+  </ItemGroup>
+</Project>

+ 92 - 0
src/PixiEditor.DrawingApi.Core/Surface/BlendMode.cs

@@ -0,0 +1,92 @@
+namespace PixiEditor.DrawingApi.Core.Surface
+{
+    public enum BlendMode
+  {
+    /// <summary>No regions are enabled. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_clr.svg)</summary>
+    Clear,
+    
+    /// <summary>Only the source will be present. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src.svg)</summary>
+    Src,
+    
+    /// <summary>Only the destination will be present. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst.svg)</summary>
+    Dst,
+    
+    /// <summary>Source is placed over the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-over.svg)</summary>
+    SrcOver,
+    
+    /// <summary>Destination is placed over the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-over.svg)</summary>
+    DstOver,
+    
+    /// <summary>The source that overlaps the destination, replaces the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-in.svg)</summary>
+    SrcIn,
+    
+    /// <summary>Destination which overlaps the source, replaces the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-in.svg)</summary>
+    DstIn,
+    
+    /// <summary>Source is placed, where it falls outside of the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-out.svg)</summary>
+    SrcOut,
+    
+    /// <summary>Destination is placed, where it falls outside of the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-out.svg)</summary>
+    DstOut,
+    
+    /// <summary>Source which overlaps the destination, replaces the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-atop.svg)</summary>
+    SrcATop,
+    
+    /// <summary>Destination which overlaps the source replaces the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-atop.svg)</summary>
+    DstATop,
+    
+    /// <summary>The non-overlapping regions of source and destination are combined. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_xor.svg)</summary>
+    Xor,
+    
+    /// <summary>Display the sum of the source image and destination image. [Porter Duff Compositing Operators]</summary>
+    Plus,
+    
+    /// <summary>Multiplies all components (= alpha and color). [Separable Blend Modes]</summary>
+    Modulate,
+    
+    /// <summary>Multiplies the complements of the backdrop and source color values, then complements the result. [Separable Blend Modes]</summary>
+    Screen,
+    
+    /// <summary>Multiplies or screens the colors, depending on the backdrop color value. [Separable Blend Modes]</summary>
+    Overlay,
+    
+    /// <summary>Selects the darker of the backdrop and source colors. [Separable Blend Modes]</summary>
+    Darken,
+    
+    /// <summary>Selects the lighter of the backdrop and source colors. [Separable Blend Modes]</summary>
+    Lighten,
+    
+    /// <summary>Brightens the backdrop color to reflect the source color. [Separable Blend Modes]</summary>
+    ColorDodge,
+    
+    /// <summary>Darkens the backdrop color to reflect the source color. [Separable Blend Modes]</summary>
+    ColorBurn,
+    
+    /// <summary>Multiplies or screens the colors, depending on the source color value. [Separable Blend Modes]</summary>
+    HardLight,
+    
+    /// <summary>Darkens or lightens the colors, depending on the source color value. [Separable Blend Modes]</summary>
+    SoftLight,
+    
+    /// <summary>Subtracts the darker of the two constituent colors from the lighter color. [Separable Blend Modes]</summary>
+    Difference,
+    
+    /// <summary>Produces an effect similar to that of the Difference mode but lower in contrast. [Separable Blend Modes]</summary>
+    Exclusion,
+    
+    /// <summary>The source color is multiplied by the destination color and replaces the destination [Separable Blend Modes]</summary>
+    Multiply,
+    
+    /// <summary>Creates a color with the hue of the source color and the saturation and luminosity of the backdrop color. [Non-Separable Blend Modes]</summary>
+    Hue,
+    
+    /// <summary>Creates a color with the saturation of the source color and the hue and luminosity of the backdrop color. [Non-Separable Blend Modes]</summary>
+    Saturation,
+    
+    /// <summary>Creates a color with the hue and saturation of the source color and the luminosity of the backdrop color. [Non-Separable Blend Modes]</summary>
+    Color,
+    
+    /// <summary>Creates a color with the luminosity of the source color and the hue and saturation of the backdrop color. [Non-Separable Blend Modes]</summary>
+    Luminosity,
+  }
+}

+ 14 - 0
src/PixiEditor.DrawingApi.Core/Surface/Canvas.cs

@@ -0,0 +1,14 @@
+using PixiEditor.DrawingApi.Core.Bridge;
+
+namespace PixiEditor.DrawingApi.Core.Surface
+{
+    public class Canvas
+    {
+        public void DrawPixel(int posX, int posY, Paint drawingPaint) => DrawingBackendApi.Current.CanvasOperations.DrawPixel(posX, posY, drawingPaint);
+
+        public void DrawSurface(DrawingSurface original, int x, int y) 
+            => DrawingBackendApi.Current.CanvasOperations.DrawSurface(original, x, y);
+
+        public void DrawImage(Image image, int x, int y) => DrawingBackendApi.Current.CanvasOperations.DrawImage(image, x, y);
+    }
+}

+ 18 - 0
src/PixiEditor.DrawingApi.Core/Surface/DrawingSurface.cs

@@ -0,0 +1,18 @@
+using PixiEditor.DrawingApi.Core.Bridge;
+
+namespace PixiEditor.DrawingApi.Core.Surface
+{
+    public class DrawingSurface
+    {
+        public float Width { get; set; }
+        public float Height { get; set; }
+        
+        public DrawingSurfaceProperties Properties { get; private set; }
+        public Canvas Canvas { get; private set; }
+
+        public Image Snapshot()
+        {
+            return DrawingBackendApi.Current.ImageOperations.Snapshot(this);
+        }
+    }
+}

+ 7 - 0
src/PixiEditor.DrawingApi.Core/Surface/DrawingSurfaceProperties.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.DrawingApi.Core.Surface
+{
+    public struct DrawingSurfaceProperties
+    {
+        
+    }
+}

+ 18 - 0
src/PixiEditor.DrawingApi.Core/Surface/FilterQuality.cs

@@ -0,0 +1,18 @@
+namespace PixiEditor.DrawingApi.Core.Surface
+{
+    /// <summary>Filter quality settings.</summary>
+    public enum FilterQuality
+    {
+        /// <summary>Unspecified.</summary>
+        None,
+        
+        /// <summary>Low quality.</summary>
+        Low,
+        
+        /// <summary>Medium quality.</summary>
+        Medium,
+        
+        /// <summary>High quality.</summary>
+        High,
+    }
+}

+ 25 - 0
src/PixiEditor.DrawingApi.Core/Surface/Image.cs

@@ -0,0 +1,25 @@
+using PixiEditor.DrawingApi.Core.Bridge;
+
+namespace PixiEditor.DrawingApi.Core.Surface
+{
+    /// <summary>An abstraction for drawing a rectangle of pixels.</summary>
+    /// <remarks>
+    ///     <para>An image is an abstraction of pixels, though the particular type of image could be actually storing its data on the GPU, or as drawing commands (picture or PDF or otherwise), ready to be played back into another canvas.</para>
+    ///     <para />
+    ///     <para>The content of an image is always immutable, though the actual storage may change, if for example that image can be recreated via encoded data or other means.</para>
+    ///     <para />
+    ///     <para>An image always has a non-zero dimensions. If there is a request to create a new image, either directly or via a surface, and either of the requested dimensions are zero, then <see langword="null" /> will be returned.</para>
+    /// </remarks>
+    public class Image : PixelDataObject
+    {
+        public override void Dispose()
+        {
+            DrawingBackendApi.Current.ImageOperations.DisposeImage(this);
+        }
+
+        public static Image FromEncodedData(string path)
+        {
+            return DrawingBackendApi.Current.ImageOperations.FromEncodedData(path);
+        }
+    }
+}

+ 22 - 0
src/PixiEditor.DrawingApi.Core/Surface/Paint.cs

@@ -0,0 +1,22 @@
+using System;
+using PixiEditor.DrawingApi.Core.Bridge;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
+
+namespace PixiEditor.DrawingApi.Core.Surface
+{
+    /// <summary>
+    ///     Class used to define surface paint, which is a collection of paint operations.
+    /// </summary>
+    public class Paint : IDisposable
+    {
+        public Color Color { get; set; }
+        public BlendMode BlendMode { get; set; } = BlendMode.Src;
+        public FilterQuality FilterQuality { get; set; } = FilterQuality.None;
+        public bool IsAntiAliased { get; set; } = false;
+
+        public void Dispose()
+        {
+            DrawingBackendApi.Current.PaintOperations.Dispose(this);
+        }
+    }
+}

+ 9 - 0
src/PixiEditor.DrawingApi.Core/Surface/PixelDataObject.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace PixiEditor.DrawingApi.Core.Surface
+{
+    public abstract class PixelDataObject : IDisposable
+    {
+        public abstract void Dispose();
+    }
+}

+ 32 - 0
src/PixiEditor.sln

@@ -32,6 +32,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor.ChangeableDocume
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor.Zoombox", "PixiEditor.Zoombox\PixiEditor.Zoombox.csproj", "{69DD5830-C682-49FB-B1A5-D2A506EEA06B}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor.Zoombox", "PixiEditor.Zoombox\PixiEditor.Zoombox.csproj", "{69DD5830-C682-49FB-B1A5-D2A506EEA06B}"
 EndProject
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.DrawingApi.Core", "PixiEditor.DrawingApi.Core\PixiEditor.DrawingApi.Core.csproj", "{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}"
+EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Debug|Any CPU = Debug|Any CPU
@@ -392,6 +394,36 @@ Global
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|x64.Build.0 = Release|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|x64.Build.0 = Release|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|x86.ActiveCfg = Release|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|x86.ActiveCfg = Release|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|x86.Build.0 = Release|Any CPU
 		{69DD5830-C682-49FB-B1A5-D2A506EEA06B}.Release|x86.Build.0 = Release|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Debug|x64.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Debug|x86.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Dev Release|Any CPU.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Dev Release|Any CPU.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Dev Release|x64.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Dev Release|x64.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Dev Release|x86.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Dev Release|x86.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX Debug|x64.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX Debug|x86.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX Debug|x86.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX|Any CPU.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX|Any CPU.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX|x64.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX|x64.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX|x86.ActiveCfg = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.MSIX|x86.Build.0 = Debug|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Release|x64.ActiveCfg = Release|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Release|x64.Build.0 = Release|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Release|x86.ActiveCfg = Release|Any CPU
+		{5FC5E9C5-F439-43AA-92AF-9B7554D6FA13}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE

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

@@ -189,7 +189,7 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
         
         
         public SurfaceBuilder WithImage(ReadOnlySpan<byte> buffer, int x, int y)
         public SurfaceBuilder WithImage(ReadOnlySpan<byte> buffer, int x, int y)
         {
         {
-            Surface.SkiaSurface.Canvas.DrawBitmap(SKBitmap.Decode(buffer), 0, 0);
+            Surface.DrawingSurface.Canvas.DrawBitmap(SKBitmap.Decode(buffer), 0, 0);
             return this;
             return this;
         }
         }
     }
     }

+ 1 - 1
src/PixiEditor/Helpers/SurfaceHelpers.cs

@@ -46,7 +46,7 @@ public static class SurfaceHelpers
         byte[] buffer = new byte[width * height * imageInfo.BytesPerPixel];
         byte[] buffer = new byte[width * height * imageInfo.BytesPerPixel];
         fixed (void* pointer = buffer)
         fixed (void* pointer = buffer)
         {
         {
-            if (!surface.SkiaSurface.ReadPixels(imageInfo, new IntPtr(pointer), imageInfo.RowBytes, 0, 0))
+            if (!surface.DrawingSurface.ReadPixels(imageInfo, new IntPtr(pointer), imageInfo.RowBytes, 0, 0))
             {
             {
                 throw new InvalidOperationException("Could not read surface into buffer");
                 throw new InvalidOperationException("Could not read surface into buffer");
             }
             }

+ 1 - 1
src/PixiEditor/Models/Controllers/ClipboardController.cs

@@ -41,7 +41,7 @@ internal static class ClipboardController
         var (actuallySurface, _) = surface.AsT2;
         var (actuallySurface, _) = surface.AsT2;
         DataObject data = new DataObject();
         DataObject data = new DataObject();
 
 
-        using (SKData pngData = actuallySurface.SkiaSurface.Snapshot().Encode())
+        using (SKData pngData = actuallySurface.DrawingSurface.Snapshot().Encode())
         {
         {
             // Stream should not be disposed
             // Stream should not be disposed
             MemoryStream pngStream = new MemoryStream();
             MemoryStream pngStream = new MemoryStream();

+ 1 - 1
src/PixiEditor/Models/IO/Exporter.cs

@@ -138,7 +138,7 @@ internal class Exporter
         var bytes = new byte[rectToSave.Width * rectToSave.Height * 8 + 8];
         var bytes = new byte[rectToSave.Width * rectToSave.Height * 8 + 8];
         try
         try
         {
         {
-            surface.SkiaSurface.ReadPixels(imageInfo, unmanagedBuffer, rectToSave.Width * 8, rectToSave.Left, rectToSave.Top);
+            surface.DrawingSurface.ReadPixels(imageInfo, unmanagedBuffer, rectToSave.Width * 8, rectToSave.Left, rectToSave.Top);
             Marshal.Copy(unmanagedBuffer, bytes, 8, rectToSave.Width * rectToSave.Height * 8);
             Marshal.Copy(unmanagedBuffer, bytes, 8, rectToSave.Width * rectToSave.Height * 8);
         }
         }
         finally
         finally

+ 1 - 1
src/PixiEditor/Models/IO/Importer.cs

@@ -95,7 +95,7 @@ internal class Importer : NotifyableObject
             SKSurface surface = SKSurface.Create(map);
             SKSurface surface = SKSurface.Create(map);
             Surface finalSurface = new Surface(new VecI(width, height));
             Surface finalSurface = new Surface(new VecI(width, height));
             using SKPaint paint = new() { BlendMode = SKBlendMode.Src };
             using SKPaint paint = new() { BlendMode = SKBlendMode.Src };
-            surface.Draw(finalSurface.SkiaSurface.Canvas, 0, 0, paint);
+            surface.Draw(finalSurface.DrawingSurface.Canvas, 0, 0, paint);
             return finalSurface;
             return finalSurface;
         }
         }
         finally
         finally

+ 2 - 2
src/PixiEditor/Models/Rendering/WriteableBitmapUpdater.cs

@@ -227,7 +227,7 @@ internal class WriteableBitmapUpdater
                     OneOf<Chunk, EmptyChunk> rendered = ChunkRenderer.MergeWholeStructure(chunk, ChunkResolution.Full, folder);
                     OneOf<Chunk, EmptyChunk> rendered = ChunkRenderer.MergeWholeStructure(chunk, ChunkResolution.Full, folder);
                     if (rendered.IsT0)
                     if (rendered.IsT0)
                     {
                     {
-                        memberVM.PreviewSurface.Canvas.DrawSurface(rendered.AsT0.Surface.SkiaSurface, pos, SmoothReplacingPaint);
+                        memberVM.PreviewSurface.Canvas.DrawSurface(rendered.AsT0.Surface.DrawingSurface, pos, SmoothReplacingPaint);
                         rendered.AsT0.Dispose();
                         rendered.AsT0.Dispose();
                     }
                     }
                     else
                     else
@@ -288,7 +288,7 @@ internal class WriteableBitmapUpdater
         ChunkRenderer.MergeWholeStructure(chunkPos, resolution, internals.Tracker.Document.StructureRoot).Switch(
         ChunkRenderer.MergeWholeStructure(chunkPos, resolution, internals.Tracker.Document.StructureRoot).Switch(
             (Chunk chunk) =>
             (Chunk chunk) =>
             {
             {
-                screenSurface.Canvas.DrawSurface(chunk.Surface.SkiaSurface, chunkPos.Multiply(chunk.PixelSize), ReplacingPaint);
+                screenSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, chunkPos.Multiply(chunk.PixelSize), ReplacingPaint);
                 chunk.Dispose();
                 chunk.Dispose();
             },
             },
             (EmptyChunk _) =>
             (EmptyChunk _) =>

+ 4 - 4
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -290,18 +290,18 @@ internal class DocumentViewModel : NotifyableObject
 
 
         SKPath clipPath = new SKPath(SelectionPathBindable) { FillType = SKPathFillType.EvenOdd };
         SKPath clipPath = new SKPath(SelectionPathBindable) { FillType = SKPathFillType.EvenOdd };
         clipPath.Transform(SKMatrix.CreateTranslation(-bounds.X, -bounds.Y));
         clipPath.Transform(SKMatrix.CreateTranslation(-bounds.X, -bounds.Y));
-        output.SkiaSurface.Canvas.Save();
-        output.SkiaSurface.Canvas.ClipPath(clipPath);
+        output.DrawingSurface.Canvas.Save();
+        output.DrawingSurface.Canvas.ClipPath(clipPath);
         try
         try
         {
         {
-            layer.LayerImage.DrawMostUpToDateRegionOn(bounds, ChunkResolution.Full, output.SkiaSurface, VecI.Zero);
+            layer.LayerImage.DrawMostUpToDateRegionOn(bounds, ChunkResolution.Full, output.DrawingSurface, VecI.Zero);
         }
         }
         catch (ObjectDisposedException)
         catch (ObjectDisposedException)
         {
         {
             output.Dispose();
             output.Dispose();
             return new Error();
             return new Error();
         }
         }
-        output.SkiaSurface.Canvas.Restore();
+        output.DrawingSurface.Canvas.Restore();
 
 
         return (output, bounds);
         return (output, bounds);
     }
     }