Browse Source

Skia direct renderer wip

Krzysztof Krysiński 1 year ago
parent
commit
7f7828c17a
53 changed files with 391 additions and 164 deletions
  1. 28 0
      src/ChunkyImageLib/Surface.cs
  2. 1 1
      src/Directory.Build.props
  3. 3 4
      src/PixiEditor.AvaloniaUI/Helpers/DocumentViewModelBuilder.cs
  4. 0 31
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/ActionAccumulator.cs
  5. 6 8
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs
  6. 3 1
      src/PixiEditor.AvaloniaUI/Models/DocumentModels/Public/DocumentOperationsModule.cs
  7. 3 4
      src/PixiEditor.AvaloniaUI/Models/Handlers/IDocument.cs
  8. 3 1
      src/PixiEditor.AvaloniaUI/Models/Handlers/IReferenceLayerHandler.cs
  9. 3 2
      src/PixiEditor.AvaloniaUI/Models/IO/Importer.cs
  10. 7 7
      src/PixiEditor.AvaloniaUI/Models/Rendering/CanvasUpdater.cs
  11. 12 11
      src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs
  12. 14 19
      src/PixiEditor.AvaloniaUI/Models/UserData/RecentlyOpenedDocument.cs
  13. 2 1
      src/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj
  14. 12 32
      src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs
  15. 6 3
      src/PixiEditor.AvaloniaUI/ViewModels/Document/ReferenceLayerViewModel.cs
  16. 8 7
      src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/ClipboardViewModel.cs
  17. 7 5
      src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/FixedViewport.axaml
  18. 5 3
      src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/FixedViewport.axaml.cs
  19. 14 14
      src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/Viewport.axaml
  20. 9 5
      src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/Viewport.axaml.cs
  21. 151 0
      src/PixiEditor.AvaloniaUI/Views/Visuals/SurfaceControl.cs
  22. 8 2
      src/PixiEditor.AvaloniaUI/Views/Windows/HelloTherePopup.axaml
  23. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IBitmapImplementation.cs
  24. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IColorFilterImplementation.cs
  25. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IColorSpaceImplementation.cs
  26. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IImgDataImplementation.cs
  27. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPaintImplementation.cs
  28. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IPixmapImplementation.cs
  29. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IVectorPathImplementation.cs
  30. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/ICanvasImplementation.cs
  31. 1 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/IImageImplementation.cs
  32. 2 0
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/ISurfaceImplementation.cs
  33. 3 1
      src/PixiEditor.DrawingApi.Core/Surface/Bitmap.cs
  34. 4 1
      src/PixiEditor.DrawingApi.Core/Surface/Canvas.cs
  35. 3 1
      src/PixiEditor.DrawingApi.Core/Surface/DrawingSurface.cs
  36. 2 0
      src/PixiEditor.DrawingApi.Core/Surface/ImageData/ColorSpace.cs
  37. 2 0
      src/PixiEditor.DrawingApi.Core/Surface/ImageData/Image.cs
  38. 2 0
      src/PixiEditor.DrawingApi.Core/Surface/ImageData/ImgData.cs
  39. 1 0
      src/PixiEditor.DrawingApi.Core/Surface/NativeObject.cs
  40. 1 0
      src/PixiEditor.DrawingApi.Core/Surface/PaintImpl/ColorFilter.cs
  41. 2 0
      src/PixiEditor.DrawingApi.Core/Surface/PaintImpl/Paint.cs
  42. 2 0
      src/PixiEditor.DrawingApi.Core/Surface/Pixmap.cs
  43. 2 0
      src/PixiEditor.DrawingApi.Core/Surface/Vector/VectorPath.cs
  44. 5 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaBitmapImplementation.cs
  45. 5 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaCanvasImplementation.cs
  46. 5 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaColorFilterImplementation.cs
  47. 5 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaColorSpaceImplementation.cs
  48. 5 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaImageImplementation.cs
  49. 5 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaImgDataImplementation.cs
  50. 5 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPaintImplementation.cs
  51. 5 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPathImplementation.cs
  52. 5 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPixmapImplementation.cs
  53. 6 0
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaSurfaceImplementation.cs

+ 28 - 0
src/ChunkyImageLib/Surface.cs

@@ -86,6 +86,26 @@ public class Surface : IDisposable
         }
     }
 
+    public Surface Resize(VecI newSize, ResizeMethod resizeMethod)
+    {
+        using Image image = DrawingSurface.Snapshot();
+        Surface newSurface = new(newSize);
+        using Paint paint = new();
+
+        FilterQuality filterQuality = resizeMethod switch
+        {
+            ResizeMethod.HighQuality => FilterQuality.High,
+            ResizeMethod.MediumQuality => FilterQuality.Medium,
+            ResizeMethod.LowQuality => FilterQuality.Low,
+            _ => FilterQuality.None
+        };
+
+        paint.FilterQuality = filterQuality;
+
+        newSurface.DrawingSurface.Canvas.DrawImage(image, new RectD(0, 0, newSize.X, newSize.Y), paint);
+        return newSurface;
+    }
+
     public Surface ResizeNearestNeighbor(VecI newSize)
     {
         using Image image = DrawingSurface.Snapshot();
@@ -178,3 +198,11 @@ public class Surface : IDisposable
         Marshal.FreeHGlobal(PixelBuffer);
     }
 }
+
+public enum ResizeMethod
+{
+    NearestNeighbor,
+    HighQuality,
+    MediumQuality,
+    LowQuality,
+}

+ 1 - 1
src/Directory.Build.props

@@ -1,7 +1,7 @@
 <Project>
     <PropertyGroup>
         <CodeAnalysisRuleSet>../Custom.ruleset</CodeAnalysisRuleSet>
-		<AvaloniaVersion>11.0.6</AvaloniaVersion>
+		<AvaloniaVersion>11.0.10</AvaloniaVersion>
     </PropertyGroup>
     <ItemGroup>
         <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />

+ 3 - 4
src/PixiEditor.AvaloniaUI/Helpers/DocumentViewModelBuilder.cs

@@ -343,10 +343,9 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
 
         public ReferenceLayerBuilder WithSurface(Surface surface)
         {
-            var writeableBitmap = surface.ToWriteableBitmap();
-            using var frameBuffer = writeableBitmap.Lock();
-            byte[] bytes = new byte[writeableBitmap.PixelSize.Height * frameBuffer.RowBytes];
-            Marshal.Copy(frameBuffer.Address, bytes, 0, bytes.Length);
+            // TODO: Make sure this works, there was WriteableBitmap previously
+            byte[] bytes = new byte[surface.Size.Y * surface.BytesPerPixel];
+            Marshal.Copy(surface.PixelBuffer, bytes, 0, bytes.Length);
 
             WithImage(surface.Size, bytes);
             

+ 0 - 31
src/PixiEditor.AvaloniaUI/Models/DocumentModels/ActionAccumulator.cs

@@ -58,8 +58,6 @@ internal class ActionAccumulator
             document.Busy = true;
         };
         busyTimer.Start();
-        
-        List<ILockedFramebuffer> lockedFramebuffers = new();
 
         while (queuedActions.Count > 0)
         {
@@ -84,12 +82,6 @@ internal class ActionAccumulator
             }
             if (undoBoundaryPassed)
                 internals.Updater.AfterUndoBoundaryPassed();
-            
-            // lock bitmaps
-            foreach (var (_, bitmap) in document.LazyBitmaps)
-            {
-                lockedFramebuffers.Add(bitmap.Lock());
-            }
 
             // update the contents of the bitmaps
             var affectedAreas = new AffectedAreasGatherer(internals.Tracker, optimizedChanges);
@@ -97,20 +89,9 @@ internal class ActionAccumulator
             renderResult.AddRange(await canvasUpdater.UpdateGatheredChunks(affectedAreas, undoBoundaryPassed || viewportRefreshRequest));
             renderResult.AddRange(await previewUpdater.UpdateGatheredChunks(affectedAreas, undoBoundaryPassed));
 
-            if (undoBoundaryPassed)
-                LockPreviewBitmaps(document.StructureRoot, lockedFramebuffers);
-
             // add dirty rectangles
             AddDirtyRects(renderResult);
 
-            // unlock bitmaps
-            foreach (var lockedFramebuffer in lockedFramebuffers)
-            {
-                lockedFramebuffer?.Dispose();
-            }
-
-            lockedFramebuffers.Clear();
-
             // force refresh viewports for better responsiveness
             foreach (var (_, value) in internals.State.Viewports)
             {
@@ -135,18 +116,6 @@ internal class ActionAccumulator
         return true;
     }
 
-    private void LockPreviewBitmaps(IFolderHandler root, List<ILockedFramebuffer> lockedFramebuffers)
-    {
-        foreach (var child in root.Children)
-        {
-            lockedFramebuffers.Add(child.PreviewBitmap?.Lock());
-            lockedFramebuffers.Add(child.MaskPreviewBitmap?.Lock());
-            if (child is IFolderHandler innerFolder)
-                LockPreviewBitmaps(innerFolder, lockedFramebuffers);
-        }
-        lockedFramebuffers.Add(document.PreviewBitmap.Lock());
-    }
-
     private void AddDirtyRects(List<IRenderInfo> changes)
     {
         //TODO: Avalonia doesn't seem to have a way to add dirty rects to bitmaps

+ 6 - 8
src/PixiEditor.AvaloniaUI/Models/DocumentModels/DocumentUpdater.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using Avalonia.Media.Imaging;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.DocumentPassthroughActions;
@@ -14,6 +15,7 @@ using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 
 namespace PixiEditor.AvaloniaUI.Models.DocumentModels;
 #nullable enable
@@ -275,24 +277,20 @@ internal class DocumentUpdater
     {
         VecI oldSize = doc.SizeBindable;
 
-        Dictionary<ChunkResolution, WriteableBitmap> newBitmaps = new();
-        foreach ((ChunkResolution res, DrawingSurface surf) in doc.Surfaces)
+        foreach ((ChunkResolution res, Surface surf) in doc.Surfaces)
         {
             surf.Dispose();
-            newBitmaps[res] = WriteableBitmapUtility.CreateBitmap((VecI)(info.Size * res.Multiplier()));
-            doc.Surfaces[res] = WriteableBitmapUtility.CreateDrawingSurface(newBitmaps[res]);
+            VecI size = (VecI)(info.Size * res.Multiplier());
+            doc.Surfaces[res] = new Surface(size); //TODO: Bgra8888 was here
         }
 
-        doc.LazyBitmaps = newBitmaps;
-
         doc.SetSize(info.Size);
         doc.SetVerticalSymmetryAxisX(info.VerticalSymmetryAxisX);
         doc.SetHorizontalSymmetryAxisY(info.HorizontalSymmetryAxisY);
 
         VecI documentPreviewSize = StructureHelpers.CalculatePreviewSize(info.Size);
         doc.PreviewSurface.Dispose();
-        doc.PreviewBitmap = WriteableBitmapUtility.CreateBitmap(documentPreviewSize);
-        doc.PreviewSurface = WriteableBitmapUtility.CreateDrawingSurface(doc.PreviewBitmap);
+        doc.PreviewSurface = new Surface(documentPreviewSize); //TODO: Bgra8888 was here
 
         // TODO: Make sure property changed events are raised internally
         // UPDATE: I think I did, but I'll leave it commented out for now

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

@@ -548,7 +548,9 @@ internal class DocumentOperationsModule : IDocumentOperations
         if (Document.ReferenceLayerHandler.ReferenceBitmap is null || Internals.ChangeController.IsChangeActive)
             return;
 
-        VecD size = new(Document.ReferenceLayerHandler.ReferenceBitmap.PixelSize.Width, Document.ReferenceLayerHandler.ReferenceBitmap.PixelSize.Height);
+
+
+        VecD size = new(Document.ReferenceLayerHandler.ReferenceBitmap.Size.X, Document.ReferenceLayerHandler.ReferenceBitmap.Size.Y);
         RectD referenceImageRect = new RectD(VecD.Zero, Document.SizeBindable).AspectFit(new RectD(VecD.Zero, size));
         ShapeCorners corners = new ShapeCorners(referenceImageRect);
         Internals.ActionAccumulator.AddFinishedActions(

+ 3 - 4
src/PixiEditor.AvaloniaUI/Models/Handlers/IDocument.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using Avalonia.Media.Imaging;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.DocumentModels.Public;
@@ -21,14 +22,12 @@ internal interface IDocument : IHandler
     public IReferenceLayerHandler ReferenceLayerHandler { get; }
     public VectorPath SelectionPathBindable { get; }
     public IFolderHandler StructureRoot { get; }
-    public Dictionary<ChunkResolution, DrawingSurface> Surfaces { get; set; }
+    public Dictionary<ChunkResolution, Surface> Surfaces { get; set; }
     public DocumentStructureModule StructureHelper { get; }
-    public DrawingSurface PreviewSurface { get; set; }
+    public Surface PreviewSurface { get; set; }
     public bool AllChangesSaved { get; }
     public string CoordinatesString { get; set; }
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers { get; }
-    public Dictionary<ChunkResolution, WriteableBitmap> LazyBitmaps { get; set; }
-    public WriteableBitmap PreviewBitmap { get; set; }
     public ILayerHandlerFactory LayerHandlerFactory { get; }
     public IFolderHandlerFactory FolderHandlerFactory { get; }
     public ITransformHandler TransformHandler { get; }

+ 3 - 1
src/PixiEditor.AvaloniaUI/Models/Handlers/IReferenceLayerHandler.cs

@@ -1,14 +1,16 @@
 using System.Collections.Immutable;
 using Avalonia;
 using Avalonia.Media.Imaging;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 
 namespace PixiEditor.AvaloniaUI.Models.Handlers;
 
 public interface IReferenceLayerHandler : IHandler
 {
-    public WriteableBitmap? ReferenceBitmap { get; }
+    public Surface? ReferenceBitmap { get; }
     public ShapeCorners ReferenceShapeBindable { get; set; }
     public bool IsTopMost { get; set; }
     public bool IsTransforming { get; set; }

+ 3 - 2
src/PixiEditor.AvaloniaUI/Models/IO/Importer.cs

@@ -135,13 +135,14 @@ internal class Importer : ObservableObject
         }
     }
 
-    public static WriteableBitmap GetPreviewBitmap(string path)
+    public static Surface GetPreviewBitmap(string path)
     {
         if (!IsSupportedFile(path))
         {
             throw new InvalidFileTypeException(new LocalizedString("FILE_EXTENSION_NOT_SUPPORTED", Path.GetExtension(path)));
         }
-        return Path.GetExtension(path) != ".pixi" ? ImportWriteableBitmap(path) : PixiParser.Deserialize(path).ToDocument().PreviewBitmap;
+
+        return Path.GetExtension(path) != ".pixi" ? Surface.Load(path) : PixiParser.Deserialize(path).ToDocument().PreviewSurface;
     }
 
     public static bool IsSupportedFile(string path)

+ 7 - 7
src/PixiEditor.AvaloniaUI/Models/Rendering/CanvasUpdater.cs

@@ -169,7 +169,7 @@ internal class CanvasUpdater
             if (globalClippingRectangle is not null)
                 globalScaledClippingRectangle = (RectI?)((RectI)globalClippingRectangle).Scale(resolution.Multiplier()).RoundOutwards();
 
-            DrawingSurface screenSurface = doc.Surfaces[resolution];
+            Surface screenSurface = doc.Surfaces[resolution];
             foreach (var chunkPos in chunks)
             {
                 RenderChunk(chunkPos, screenSurface, resolution, globalClippingRectangle, globalScaledClippingRectangle);
@@ -186,27 +186,27 @@ internal class CanvasUpdater
         }
     }
 
-    private void RenderChunk(VecI chunkPos, DrawingSurface screenSurface, ChunkResolution resolution, RectI? globalClippingRectangle, RectI? globalScaledClippingRectangle)
+    private void RenderChunk(VecI chunkPos, Surface screenSurface, ChunkResolution resolution, RectI? globalClippingRectangle, RectI? globalScaledClippingRectangle)
     {
         if (globalScaledClippingRectangle is not null)
         {
-            screenSurface.Canvas.Save();
-            screenSurface.Canvas.ClipRect((RectD)globalScaledClippingRectangle);
+            screenSurface.DrawingSurface.Canvas.Save();
+            screenSurface.DrawingSurface.Canvas.ClipRect((RectD)globalScaledClippingRectangle);
         }
 
         ChunkRenderer.MergeWholeStructure(chunkPos, resolution, internals.Tracker.Document.StructureRoot, globalClippingRectangle).Switch(
             (Chunk chunk) =>
             {
-                screenSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, chunkPos.Multiply(chunk.PixelSize), ReplacingPaint);
+                screenSurface.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, chunkPos.Multiply(chunk.PixelSize), ReplacingPaint);
                 chunk.Dispose();
             },
             (EmptyChunk _) =>
             {
                 var pos = chunkPos * resolution.PixelSize();
-                screenSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(), resolution.PixelSize(), ClearPaint);
+                screenSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(), resolution.PixelSize(), ClearPaint);
             });
 
         if (globalScaledClippingRectangle is not null)
-            screenSurface.Canvas.Restore();
+            screenSurface.DrawingSurface.Canvas.Restore();
     }
 }

+ 12 - 11
src/PixiEditor.AvaloniaUI/Models/Rendering/MemberPreviewUpdater.cs

@@ -14,6 +14,7 @@ using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 namespace PixiEditor.AvaloniaUI.Models.Rendering;
@@ -222,8 +223,8 @@ internal class MemberPreviewUpdater
                 else
                 {
                     member.PreviewSurface?.Dispose();
-                    member.PreviewBitmap = WriteableBitmapUtility.CreateBitmap(newSize.Value.previewSize);
-                    member.PreviewSurface = WriteableBitmapUtility.CreateDrawingSurface(member.PreviewBitmap);
+                    member.PreviewSurface = DrawingSurface.Create(new ImageInfo(newSize.Value.previewSize.X,
+                        newSize.Value.previewSize.Y, ColorType.Bgra8888, AlphaType.Premul));
                 }
             }
 
@@ -244,8 +245,8 @@ internal class MemberPreviewUpdater
             }
             else
             {
-                member.MaskPreviewBitmap = WriteableBitmapUtility.CreateBitmap(newSize.Value.previewSize);
-                member.MaskPreviewSurface = WriteableBitmapUtility.CreateDrawingSurface(member.MaskPreviewBitmap);
+                member.MaskPreviewSurface = DrawingSurface.Create(new ImageInfo(newSize.Value.previewSize.X,
+                    newSize.Value.previewSize.Y, ColorType.Bgra8888, AlphaType.Premul));
             }
 
             //TODO: Make sure MaskPreviewBitmap implementation raises PropertyChanged
@@ -415,20 +416,20 @@ internal class MemberPreviewUpdater
             };
             var pos = chunkPos * resolution.PixelSize();
             var rendered = ChunkRenderer.MergeWholeStructure(chunkPos, resolution, internals.Tracker.Document.StructureRoot);
-            doc.PreviewSurface.Canvas.Save();
-            doc.PreviewSurface.Canvas.Scale(scaling);
-            doc.PreviewSurface.Canvas.ClipRect((RectD)cumulative.GlobalArea);
-            doc.PreviewSurface.Canvas.Scale(1 / (float)resolution.Multiplier());
+            doc.PreviewSurface.DrawingSurface.Canvas.Save();
+            doc.PreviewSurface.DrawingSurface.Canvas.Scale(scaling);
+            doc.PreviewSurface.DrawingSurface.Canvas.ClipRect((RectD)cumulative.GlobalArea);
+            doc.PreviewSurface.DrawingSurface.Canvas.Scale(1 / (float)resolution.Multiplier());
             if (rendered.IsT1)
             {
-                doc.PreviewSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(), resolution.PixelSize(), ClearPaint);
+                doc.PreviewSurface.DrawingSurface.Canvas.DrawRect(pos.X, pos.Y, resolution.PixelSize(), resolution.PixelSize(), ClearPaint);
             }
             else if (rendered.IsT0)
             {
                 using var renderedChunk = rendered.AsT0;
-                renderedChunk.DrawOnSurface(doc.PreviewSurface, pos, SmoothReplacingPaint);
+                renderedChunk.DrawOnSurface(doc.PreviewSurface.DrawingSurface, pos, SmoothReplacingPaint);
             }
-            doc.PreviewSurface.Canvas.Restore();
+            doc.PreviewSurface.DrawingSurface.Canvas.Restore();
         }
         if (somethingChanged)
             infos.Add(new CanvasPreviewDirty_RenderInfo());

+ 14 - 19
src/PixiEditor.AvaloniaUI/Models/UserData/RecentlyOpenedDocument.cs

@@ -1,13 +1,9 @@
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
-using Avalonia;
-using Avalonia.Media.Imaging;
 using ChunkyImageLib;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.AvaloniaUI.Helpers;
-using PixiEditor.AvaloniaUI.Helpers.Extensions;
-using PixiEditor.AvaloniaUI.Models.IO;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Exceptions;
 using PixiEditor.Parser;
@@ -22,7 +18,7 @@ internal class RecentlyOpenedDocument : ObservableObject
 
     private string filePath;
 
-    private Bitmap previewBitmap;
+    private Surface previewBitmap;
 
     public string FilePath
     {
@@ -63,7 +59,7 @@ internal class RecentlyOpenedDocument : ObservableObject
         }
     }
 
-    public Bitmap PreviewBitmap
+    public Surface PreviewBitmap
     {
         get
         {
@@ -82,7 +78,7 @@ internal class RecentlyOpenedDocument : ObservableObject
         FilePath = path;
     }
 
-    private Bitmap LoadPreviewBitmap()
+    private Surface? LoadPreviewBitmap()
     {
         if (!File.Exists(FilePath))
         {
@@ -102,8 +98,7 @@ internal class RecentlyOpenedDocument : ObservableObject
                     return null;
                 }
 
-                using var data = new MemoryStream(document.PreviewImage);
-                return WriteableBitmap.Decode(data);
+                return Surface.Load(document.PreviewImage);
             }
             catch
             {
@@ -119,21 +114,21 @@ internal class RecentlyOpenedDocument : ObservableObject
                 }
             }
 
-            using Surface surface = Surface.Combine(serializableDocument.Width, serializableDocument.Height,
+            Surface surface = Surface.Combine(serializableDocument.Width, serializableDocument.Height,
                 serializableDocument.Layers
                     .Where(x => x.Opacity > 0.8)
                     .Select(x => (x.ToImage(), new VecI(x.OffsetX, x.OffsetY))).ToList());
 
-            return DownscaleToMaxSize(surface.ToWriteableBitmap());
+            return DownscaleToMaxSize(surface);
         }
 
         if (SupportedFilesHelper.IsExtensionSupported(FileExtension))
         {
-            Bitmap bitmap = null;
+            Surface bitmap = null;
 
             try
             {
-                bitmap = Importer.ImportBitmap(FilePath);
+                bitmap = Surface.Load(FilePath);
             }
             catch (RecoverableException)
             {
@@ -150,14 +145,14 @@ internal class RecentlyOpenedDocument : ObservableObject
         return null;
     }
 
-    private Bitmap DownscaleToMaxSize(Bitmap bitmap)
+    private Surface DownscaleToMaxSize(Surface bitmap)
     {
-        if (bitmap.PixelSize.Width > Constants.MaxPreviewWidth || bitmap.PixelSize.Height > Constants.MaxPreviewHeight)
+        if (bitmap.Size.X > Constants.MaxPreviewWidth || bitmap.Size.Y > Constants.MaxPreviewHeight)
         {
-            double factor = Math.Min(Constants.MaxPreviewWidth / (double)bitmap.PixelSize.Width, Constants.MaxPreviewHeight / (double)bitmap.PixelSize.Height);
-            var scaledBitmap = bitmap.CreateScaledBitmap(new PixelSize((int)(bitmap.PixelSize.Width * factor), (int)(bitmap.PixelSize.Height * factor)),
-                BitmapInterpolationMode.HighQuality);
-            return scaledBitmap.ToWriteableBitmap();
+            double factor = Math.Min(Constants.MaxPreviewWidth / (double)bitmap.Size.X, Constants.MaxPreviewHeight / (double)bitmap.Size.Y);
+            var scaledBitmap = bitmap.Resize(new VecI((int)(bitmap.Size.X * factor), (int)(bitmap.Size.Y * factor)),
+                ResizeMethod.HighQuality);
+            return scaledBitmap;
         }
 
         return bitmap;

+ 2 - 1
src/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj

@@ -26,7 +26,8 @@
         <PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
         <PackageReference Include="Avalonia.Headless" Version="$(AvaloniaVersion)" />
         <PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
-        <PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
+      <PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
+      <PackageReference Include="Avalonia.Skia" Version="$(AvaloniaVersion)" />
         <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
         <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
         <PackageReference Include="ByteSize" Version="2.1.1" />

+ 12 - 32
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -26,6 +26,7 @@ using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.DrawingApi.Core.Surface.Vector;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.Palettes;
@@ -131,29 +132,16 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     public DocumentEventsModule EventInlet { get; }
     public ActionDisplayList ActionDisplays { get; } = new(() => ViewModelMain.Current.NotifyToolActionDisplayChanged());
     public IStructureMemberHandler? SelectedStructureMember { get; private set; } = null;
-
-    public Dictionary<ChunkResolution, DrawingSurface> Surfaces { get; set; } = new();
-    public Dictionary<ChunkResolution, WriteableBitmap> LazyBitmaps { get; set; } = new()
+    //TODO: It was DrawingSurface before, check if it's correct
+    public Dictionary<ChunkResolution, Surface> Surfaces { get; set; } = new()
     {
-        [ChunkResolution.Full] = WriteableBitmapUtility.CreateBitmap(new VecI(64, 64)),
-        [ChunkResolution.Half] = WriteableBitmapUtility.CreateBitmap(new VecI(32, 32)),
-        [ChunkResolution.Quarter] = WriteableBitmapUtility.CreateBitmap(new VecI(16, 16)),
-        [ChunkResolution.Eighth] = WriteableBitmapUtility.CreateBitmap(new VecI(8, 8)),
+        [ChunkResolution.Full] = new Surface(new VecI(64, 64)),
+        [ChunkResolution.Half] = new Surface(new VecI(32, 32)),
+        [ChunkResolution.Quarter] = new Surface(new VecI(16, 16)),
+        [ChunkResolution.Eighth] = new Surface(new VecI(8, 8))
     };
 
-    private WriteableBitmap previewBitmap;
-
-    public WriteableBitmap PreviewBitmap
-    {
-        get => previewBitmap;
-        set
-        {
-            SetProperty(ref previewBitmap, value);
-            OnPropertyChanged(nameof(LazyBitmaps));
-        }
-    }
-
-    public DrawingSurface PreviewSurface { get; set; }
+    public Surface PreviewSurface { get; set; }
 
     private VectorPath selectionPath = new VectorPath();
     public VectorPath SelectionPathBindable => selectionPath;
@@ -194,15 +182,8 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         LineToolOverlayViewModel = new();
         LineToolOverlayViewModel.LineMoved += (_, args) => Internals.ChangeController.LineOverlayMovedInlet(args.Item1, args.Item2);
 
-        foreach (KeyValuePair<ChunkResolution, WriteableBitmap> bitmap in LazyBitmaps)
-        {
-            DrawingSurface? surface = WriteableBitmapUtility.CreateDrawingSurface(bitmap.Value);
-            Surfaces[bitmap.Key] = surface;
-        }
-
         VecI previewSize = StructureMemberViewModel.CalculatePreviewSize(SizeBindable);
-        PreviewBitmap = WriteableBitmapUtility.CreateBitmap(previewSize);
-        PreviewSurface = WriteableBitmapUtility.CreateDrawingSurface(PreviewBitmap);
+        PreviewSurface = new Surface(new VecI(previewSize.X, previewSize.Y));
 
         ReferenceLayerViewModel = new(this, Internals);
     }
@@ -448,7 +429,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
     public Color? PickColorFromReferenceLayer(VecD pos)
     {
-        WriteableBitmap? bitmap = ReferenceLayerViewModel.ReferenceBitmap; 
+        Surface? bitmap = ReferenceLayerViewModel.ReferenceBitmap;
         if (bitmap is null)
             return null;
         
@@ -456,11 +437,10 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         matrix.Invert();
         var transformed = matrix.Transform(new Point(pos.X, pos.Y));
 
-        if (transformed.X < 0 || transformed.Y < 0 || transformed.X >= bitmap.PixelSize.Width || transformed.Y >= bitmap.PixelSize.Height)
+        if (transformed.X < 0 || transformed.Y < 0 || transformed.X >= bitmap.Size.X || transformed.Y >= bitmap.Size.Y)
             return null;
 
-        using var frameBuffer = bitmap.Lock();
-        return frameBuffer.GetPixel((int)transformed.X, (int)transformed.Y).ToColor();
+        return bitmap.GetSRGBPixel(new VecI((int)transformed.X, (int)transformed.Y));
     }
 
     public Color PickColorFromCanvas(VecI pos, DocumentScope scope)

+ 6 - 3
src/PixiEditor.AvaloniaUI/ViewModels/Document/ReferenceLayerViewModel.cs

@@ -2,6 +2,7 @@
 using System.Linq;
 using Avalonia;
 using Avalonia.Media.Imaging;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using CommunityToolkit.Mvvm.ComponentModel;
@@ -11,6 +12,7 @@ using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.ViewModels.Tools.Tools;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Document;
 
@@ -22,7 +24,7 @@ internal class ReferenceLayerViewModel : ObservableObject, IReferenceLayerHandle
 
     public const double TopMostOpacity = 0.6;
     
-    public WriteableBitmap? ReferenceBitmap { get; private set; }
+    public Surface? ReferenceBitmap { get; private set; }
 
     private ShapeCorners referenceShape;
     public ShapeCorners ReferenceShapeBindable 
@@ -41,7 +43,8 @@ internal class ReferenceLayerViewModel : ObservableObject, IReferenceLayerHandle
         {
             if (ReferenceBitmap is null)
                 return Matrix.Identity;
-            Matrix3X3 skiaMatrix = OperationHelper.CreateMatrixFromPoints(ReferenceShapeBindable, new VecD(ReferenceBitmap.PixelSize.Width, ReferenceBitmap.PixelSize.Height));
+
+            Matrix3X3 skiaMatrix = OperationHelper.CreateMatrixFromPoints(ReferenceShapeBindable, new VecD(ReferenceBitmap.Size.X, ReferenceBitmap.Size.Y));
             return new Matrix(skiaMatrix.ScaleX, skiaMatrix.SkewY, skiaMatrix.SkewX, skiaMatrix.ScaleY, skiaMatrix.TransX, skiaMatrix.TransY);
         }
     }
@@ -109,7 +112,7 @@ internal class ReferenceLayerViewModel : ObservableObject, IReferenceLayerHandle
     
     public void SetReferenceLayer(ImmutableArray<byte> imageBgra8888Bytes, VecI imageSize, ShapeCorners shape)
     {
-        ReferenceBitmap = WriteableBitmapUtility.FromBgra8888Array(imageBgra8888Bytes.ToArray(), imageSize);
+        ReferenceBitmap = Surface.Load(imageBgra8888Bytes.ToArray()); //TODO: Was WriteableBitmapUtility.FromBgra8888Array(imageBgra8888Bytes.ToArray(), imageSize);
         referenceShape = shape;
         isVisible = true;
         isTransforming = false;

+ 8 - 7
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/ClipboardViewModel.cs

@@ -7,6 +7,7 @@ using Avalonia.Input;
 using Avalonia.Media;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
+using PixiEditor.AvaloniaUI.Models.Clipboard;
 using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Commands;
 using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Evaluators;
 using PixiEditor.AvaloniaUI.Models.Commands.Search;
@@ -52,16 +53,16 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
 
-        var surface = (data == null ? await ClipboardController.GetImagesFromClipboard() : ClipboardController.GetImage(data)).First();
-        using var image = surface.image;
-        
-        var bitmap = surface.image.ToWriteableBitmap();
+        DataImage imageData = (data == null ? await ClipboardController.GetImagesFromClipboard() : ClipboardController.GetImage(data)).First();
+        using var surface = imageData.image;
+
+        var bitmap = imageData.image.ToWriteableBitmap();
 
         byte[] pixels = bitmap.ExtractPixels();
 
         doc.Operations.ImportReferenceLayer(
             pixels.ToImmutableArray(),
-            surface.image.Size);
+            imageData.image.Size);
 
         if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
         {
@@ -75,11 +76,11 @@ internal class ClipboardViewModel : SubViewModel<ViewModelMain>
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
 
         var bitmap = Importer.GetPreviewBitmap(path);
-        byte[] pixels = bitmap.ExtractPixels();
+        byte[] pixels = bitmap.ToWriteableBitmap().ExtractPixels();
 
         doc.Operations.ImportReferenceLayer(
             pixels.ToImmutableArray(),
-            new VecI(bitmap.PixelSize.Width, bitmap.PixelSize.Height));
+            new VecI(bitmap.Size.X, bitmap.Size.Y));
     }
 
     [Command.Basic("PixiEditor.Clipboard.PasteColor", false, "PASTE_COLOR", "PASTE_COLOR_DESCRIPTIVE", CanExecute = "PixiEditor.Clipboard.CanPasteColor", IconEvaluator = "PixiEditor.Clipboard.PasteColorIcon")]

+ 7 - 5
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/FixedViewport.axaml

@@ -6,23 +6,25 @@
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:converters1="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters"
              xmlns:ui="clr-namespace:PixiEditor.AvaloniaUI.Helpers.UI"
+             xmlns:visuals="clr-namespace:PixiEditor.AvaloniaUI.Views.Visuals"
              mc:Ignorable="d"
              x:Name="uc"
              HorizontalAlignment="Center"
              VerticalAlignment="Center"
              d:DesignHeight="450" d:DesignWidth="800">
-    <Image
+
+    <!--TODO there was Stretch="Uniform" below-->
+    <visuals:SurfaceControl
         x:Name="mainImage"
         Focusable="True"
-        Source="{Binding TargetBitmap, ElementName=uc}"
+        Surface="{Binding TargetBitmap, ElementName=uc}"
         HorizontalAlignment="Stretch"
-        Stretch="Uniform"
         SizeChanged="OnImageSizeChanged">
         <ui:RenderOptionsBindable.BitmapInterpolationMode>
             <MultiBinding Converter="{converters1:WidthToBitmapScalingModeConverter}">
-                <Binding ElementName="uc" Path="TargetBitmap.PixelSize.Width"/>
+                <Binding ElementName="uc" Path="TargetBitmap.Size.X"/>
                 <Binding ElementName="mainImage" Path="Bounds.Width"/>
             </MultiBinding>
         </ui:RenderOptionsBindable.BitmapInterpolationMode>
-    </Image>
+    </visuals:SurfaceControl>
 </UserControl>

+ 5 - 3
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/FixedViewport.axaml.cs

@@ -5,10 +5,12 @@ using Avalonia.Controls;
 using Avalonia.Data;
 using Avalonia.Interactivity;
 using Avalonia.Media.Imaging;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Models.Position;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 
 namespace PixiEditor.AvaloniaUI.Views.Main.ViewportControls;
 
@@ -43,11 +45,11 @@ internal partial class FixedViewport : UserControl, INotifyPropertyChanged
         set => SetValue(DocumentProperty, value);
     }
 
-    public WriteableBitmap? TargetBitmap
+    public Surface? TargetBitmap
     {
         get
         {
-            if (Document?.LazyBitmaps.TryGetValue(CalculateResolution(), out WriteableBitmap? value) == true)
+            if (Document?.Surfaces.TryGetValue(CalculateResolution(), out Surface? value) == true)
                 return value;
             return null;
         }
@@ -64,7 +66,7 @@ internal partial class FixedViewport : UserControl, INotifyPropertyChanged
     public FixedViewport()
     {
         InitializeComponent();
-        Binding binding = new Binding { Source = this, Path = $"{nameof(Document)}.{nameof(Document.LazyBitmaps)}" };
+        Binding binding = new Binding { Source = this, Path = $"{nameof(Document)}.{nameof(Document.Surfaces)}" };
         this.Bind(BitmapsProperty, binding);
         Loaded += OnLoad;
         Unloaded += OnUnload;

+ 14 - 14
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/Viewport.axaml

@@ -158,22 +158,22 @@
                         ZIndex="{Binding Document.ReferenceLayerViewModel.ShowHighest, Converter={converters:BoolToIntConverter}}"
                         IsHitTestVisible="{Binding Document.ReferenceLayerViewModel.IsTransforming}"
                         ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding ReferenceLayerScale, Converter={converters:ScaleToBitmapScalingModeConverter}}">
-                        <Image
+                        <visuals:SurfaceControl
                             Focusable="False"
-                            Width="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Size.Width}"
-                            Height="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Size.Height}"
-                            Source="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, Mode=OneWay}"
+                            Width="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Size.X}"
+                            Height="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Size.Y}"
+                            Surface="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, Mode=OneWay}"
                             IsVisible="{Binding Document.ReferenceLayerViewModel.IsVisibleBindable}"
                             RenderTransformOrigin="0, 0"
                             SizeChanged="OnReferenceImageSizeChanged"
                             FlowDirection="LeftToRight">
-                            <Image.RenderTransform>
+                            <visuals:SurfaceControl.RenderTransform>
                                 <TransformGroup>
                                     <MatrixTransform
                                         Matrix="{Binding Document.ReferenceLayerViewModel.ReferenceTransformMatrix}" />
                                 </TransformGroup>
-                            </Image.RenderTransform>
-                            <Image.Styles>
+                            </visuals:SurfaceControl.RenderTransform>
+                            <visuals:SurfaceControl.Styles>
                                 <!--TODO: Implement this-->
                                 <!--<Style>
                                     <Style.Triggers>
@@ -199,8 +199,8 @@
                                         </DataTrigger>
                                     </Style.Triggers>
                                 </Style>-->
-                            </Image.Styles>
-                        </Image>
+                            </visuals:SurfaceControl.Styles>
+                        </visuals:SurfaceControl>
                         <Canvas.Styles>
                             <!--TODO: Implement this-->
                             <!--<Style>
@@ -229,14 +229,14 @@
                             </Style>-->
                         </Canvas.Styles>
                     </Canvas>
-                    <Image
+                    <visuals:SurfaceControl
                         Focusable="False"
                         Width="{Binding Document.Width}"
                         Height="{Binding Document.Height}"
-                        Source="{Binding TargetBitmap}"
+                        Surface="{Binding TargetBitmap}"
                         ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding Zoombox.Scale, Converter={converters:ScaleToBitmapScalingModeConverter}}"
                         FlowDirection="LeftToRight">
-                        <Image.Styles>
+                        <visuals:SurfaceControl.Styles>
                             <!--TODO: Implement-->
                             <!--<Style>
                                 <Style.Triggers>
@@ -262,8 +262,8 @@
                                     </DataTrigger>
                                 </Style.Triggers>
                             </Style>-->
-                        </Image.Styles>
-                    </Image>
+                        </visuals:SurfaceControl.Styles>
+                    </visuals:SurfaceControl>
                     <Grid ZIndex="5">
                         <symmetryOverlay:SymmetryOverlay
                             Focusable="False"

+ 9 - 5
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -7,13 +7,17 @@ using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Media.Imaging;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers.UI;
 using PixiEditor.AvaloniaUI.Models.Controllers.InputDevice;
 using PixiEditor.AvaloniaUI.Models.Position;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.AvaloniaUI.Views.Visuals;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.Zoombox;
+using Point = Avalonia.Point;
 
 namespace PixiEditor.AvaloniaUI.Views.Main.ViewportControls;
 
@@ -269,17 +273,17 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         }
     }
 
-    public WriteableBitmap? TargetBitmap
+    public Surface? TargetBitmap
     {
         get
         {
-            return Document?.LazyBitmaps.TryGetValue(CalculateResolution(), out WriteableBitmap? value) == true ? value : null;
+            return Document?.Surfaces.TryGetValue(CalculateResolution(), out Surface? value) == true ? value : null;
         }
     }
 
     public double ReferenceLayerScale =>
         ZoomboxScale * ((Document?.ReferenceLayerViewModel.ReferenceBitmap != null && Document?.ReferenceLayerViewModel.ReferenceShapeBindable != null)
-            ? (Document.ReferenceLayerViewModel.ReferenceShapeBindable.RectSize.X / (double)Document.ReferenceLayerViewModel.ReferenceBitmap.PixelSize.Width)
+            ? (Document.ReferenceLayerViewModel.ReferenceShapeBindable.RectSize.X / (double)Document.ReferenceLayerViewModel.ReferenceBitmap.Size.X)
             : 1);
 
     public PixiEditor.Zoombox.Zoombox Zoombox => zoombox;
@@ -300,7 +304,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
     {
         InitializeComponent();
 
-        Binding binding = new Binding { Source = this, Path = $"{nameof(Document)}.{nameof(Document.LazyBitmaps)}" };
+        Binding binding = new Binding { Source = this, Path = $"{nameof(Document)}.{nameof(Document.Surfaces)}" };
         this.Bind(BitmapsProperty, binding);
 
         MainImage!.Loaded += OnImageLoaded;
@@ -313,7 +317,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         viewportGrid.AddHandler(PointerPressedEvent, Image_MouseDown, RoutingStrategies.Bubble);
     }
 
-    public Image? MainImage => (Image?)((Grid?)((Border?)zoombox.AdditionalContent)?.Child)?.Children[1];
+    public SurfaceControl? MainImage => (SurfaceControl?)((Grid?)((Border?)zoombox.AdditionalContent)?.Child)?.Children[1];
     public Grid BackgroundGrid => viewportGrid;
 
     private void ForceRefreshFinalImage()

+ 151 - 0
src/PixiEditor.AvaloniaUI/Views/Visuals/SurfaceControl.cs

@@ -0,0 +1,151 @@
+using System.Diagnostics;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Skia;
+using ChunkyImageLib;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using Image = PixiEditor.DrawingApi.Core.Surface.ImageData.Image;
+using Point = Avalonia.Point;
+
+namespace PixiEditor.AvaloniaUI.Views.Visuals;
+
+public class SurfaceControl : Control
+{
+    public static readonly StyledProperty<Surface> SurfaceProperty = AvaloniaProperty.Register<SurfaceControl, Surface>(
+        nameof(Surface));
+
+    public static readonly StyledProperty<Stretch> StretchProperty = AvaloniaProperty.Register<SurfaceControl, Stretch>(
+        nameof(Stretch), Stretch.Uniform);
+
+    public Stretch Stretch
+    {
+        get => GetValue(StretchProperty);
+        set => SetValue(StretchProperty, value);
+    }
+
+    public Surface Surface
+    {
+        get => GetValue(SurfaceProperty);
+        set => SetValue(SurfaceProperty, value);
+    }
+
+    private DrawingSurfaceOp _drawingSurfaceOp;
+
+    static SurfaceControl()
+    {
+        AffectsRender<SurfaceControl>(SurfaceProperty, StretchProperty);
+        SurfaceProperty.Changed.AddClassHandler<SurfaceControl>(OnSurfaceChanged);
+        BoundsProperty.Changed.AddClassHandler<SurfaceControl>(BoundsChanged);
+        StretchProperty.Changed.AddClassHandler<SurfaceControl>(StretchChanged);
+    }
+
+    public override void Render(DrawingContext context)
+    {
+        if (Surface == null)
+        {
+            return;
+        }
+
+        context.Custom(_drawingSurfaceOp);
+    }
+
+    private static void StretchChanged(SurfaceControl sender, AvaloniaPropertyChangedEventArgs e)
+    {
+        if (e.NewValue is Stretch stretch)
+        {
+            sender._drawingSurfaceOp = new DrawingSurfaceOp(sender.Surface, sender.Bounds, stretch);
+        }
+    }
+
+    private static void BoundsChanged(SurfaceControl sender, AvaloniaPropertyChangedEventArgs e)
+    {
+        if (e.NewValue is Rect bounds)
+        {
+            sender._drawingSurfaceOp = new DrawingSurfaceOp(sender.Surface, bounds, sender.Stretch);
+        }
+    }
+
+    private static void OnSurfaceChanged(SurfaceControl sender, AvaloniaPropertyChangedEventArgs e)
+    {
+        if (e.NewValue is Surface surface)
+        {
+            sender._drawingSurfaceOp = new DrawingSurfaceOp(surface, sender.Bounds, sender.Stretch);
+        }
+    }
+}
+
+public class DrawingSurfaceOp : ICustomDrawOperation
+{
+    public Rect Bounds { get;  }
+    public Surface Surface { get; }
+    public Stretch Stretch { get; set; }
+
+    private SKPaint _paint = new SKPaint();
+
+    public DrawingSurfaceOp(Surface surface, Rect bounds, Stretch stretch)
+    {
+        Surface = surface;
+        Bounds = bounds;
+        Stretch = stretch;
+    }
+
+    public void Render(ImmediateDrawingContext context)
+    {
+        if (context.TryGetFeature(out ISkiaSharpApiLeaseFeature skiaSurface))
+        {
+            using var lease = skiaSurface.Lease();
+            var canvas = lease.SkCanvas;
+            canvas.Save();
+            ScaleCanvas(canvas);
+            canvas.DrawSurface((SKSurface)Surface.DrawingSurface.Native, 0, 0, _paint);
+            canvas.Restore();
+        }
+    }
+
+    private void ScaleCanvas(SKCanvas canvas)
+    {
+        if (Stretch == Stretch.Fill)
+        {
+            canvas.Scale((float)Bounds.Width / Surface.Size.X, (float)Bounds.Height / Surface.Size.Y);
+        }
+        else if (Stretch == Stretch.Uniform)
+        {
+            float scaleX = (float)Bounds.Width / Surface.Size.X;
+            float scaleY = (float)Bounds.Height / Surface.Size.Y;
+            var scale = Math.Min(scaleX, scaleY);
+            float dX = (float)Bounds.Width / 2 / scale - Surface.Size.X / 2;
+            float dY = (float)Bounds.Height / 2 / scale - Surface.Size.Y / 2;
+            canvas.Scale(scale, scale);
+            canvas.Translate(dX, dY);
+        }
+        else if (Stretch == Stretch.UniformToFill)
+        {
+            float scaleX = (float)Bounds.Width / Surface.Size.X;
+            float scaleY = (float)Bounds.Height / Surface.Size.Y;
+            var scale = Math.Max(scaleX, scaleY);
+            float dX = (float)Bounds.Width / 2 / scale - Surface.Size.X / 2;
+            float dY = (float)Bounds.Height / 2 / scale - Surface.Size.Y / 2;
+            canvas.Scale(scale, scale);
+            canvas.Translate(dX, dY);
+        }
+    }
+
+    public bool HitTest(Point p)
+    {
+        return false;
+    }
+
+    public bool Equals(ICustomDrawOperation? other)
+    {
+        return other is DrawingSurfaceOp op && op.Surface == Surface;
+    }
+
+    public void Dispose()
+    {
+
+    }
+}

+ 8 - 2
src/PixiEditor.AvaloniaUI/Views/Windows/HelloTherePopup.axaml

@@ -13,6 +13,8 @@
         xmlns:indicators1="clr-namespace:PixiEditor.AvaloniaUI.Views.Indicators"
         xmlns:newsFeed1="clr-namespace:PixiEditor.AvaloniaUI.Views.NewsFeed"
         xmlns:dialogs="clr-namespace:PixiEditor.AvaloniaUI.Views.Dialogs"
+        xmlns:visuals="clr-namespace:PixiEditor.AvaloniaUI.Views.Visuals"
+        xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp"
         mc:Ignorable="d"
         Title="Hello there!" Height="662" Width="982" MinHeight="500" MinWidth="500"
         Loaded="HelloTherePopup_OnLoaded">
@@ -116,14 +118,18 @@
                                                     
                                                     x:Name="fileButton">
                                                 <Grid Width="100" Height="100">
-                                                    <Image Source="{Binding PreviewBitmap}" x:Name="image" Margin="20">
+                                                    <visuals:SurfaceControl
+                                                        Surface="{Binding PreviewBitmap}"
+                                                        Stretch="Uniform"
+                                                        x:Name="image"
+                                                        Margin="20">
                                                         <!--<RenderOptions.BitmapInterpolationMode> TODO: Fix
                                                             <MultiBinding Converter="{converters:WidthToBitmapScalingModeConverter}">
                                                                 <Binding Path="PreviewBitmap.PixelSize.Width"/>
                                                                 <Binding ElementName="image" Path="Width"/>
                                                             </MultiBinding>
                                                         </RenderOptions.BitmapInterpolationMode>-->
-                                                    </Image>
+                                                    </visuals:SurfaceControl>
                                                     <Border Grid.Row="1" Height="8" Width="8" x:Name="extensionBorder" Margin="5"
                                                             Background="{Binding FileExtension, Converter={converters:FileExtensionToColorConverter}}" 
                                                             VerticalAlignment="Bottom" HorizontalAlignment="Right">

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

@@ -7,4 +7,5 @@ public interface IBitmapImplementation
 {
     public void Dispose(IntPtr objectPointer);
     public Bitmap Decode(ReadOnlySpan<byte> buffer);
+    public object GetNativeBitmap(IntPtr objectPointer);
 }

+ 1 - 0
src/PixiEditor.DrawingApi.Core/Bridge/NativeObjectsImpl/IColorFilterImplementation.cs

@@ -9,4 +9,5 @@ public interface IColorFilterImplementation
 {
     public IntPtr CreateBlendMode(Color color, BlendMode blendMode);
     public void Dispose(ColorFilter colorFilter);
+    public object GetNativeColorFilter(IntPtr objectPointer);
 }

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

@@ -7,4 +7,5 @@ public interface IColorSpaceImplementation
 {
     public ColorSpace CreateSrgb();
     public void Dispose(IntPtr objectPointer);
+    public object GetNativeColorSpace(IntPtr objectPointer);
 }

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

@@ -10,4 +10,5 @@ public interface IImgDataImplementation
     public void SaveTo(ImgData imgData, FileStream stream);
     public Stream AsStream(ImgData imgData);
     public ReadOnlySpan<byte> AsSpan(ImgData imgData);
+    public object GetNativeImgData(IntPtr objectPointer);
 }

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

@@ -27,5 +27,6 @@ namespace PixiEditor.DrawingApi.Core.Bridge.NativeObjectsImpl
         public ColorFilter GetColorFilter(Paint paint);
 
         public void SetColorFilter(Paint paint, ColorFilter value);
+        public object GetNativePaint(IntPtr objectPointer);
     }
 }

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

@@ -20,4 +20,5 @@ public interface IPixmapImplementation
     public int GetHeight(Pixmap pixmap);
 
     public int GetBytesSize(Pixmap pixmap);
+    public object GetNativePixmap(IntPtr objectPointer);
 }

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

@@ -38,4 +38,5 @@ public interface IVectorPathImplementation
     public string ToSvgPathData(VectorPath vectorPath);
     public bool Contains(VectorPath vectorPath, float x, float y);
     public void AddPath(VectorPath vectorPath, VectorPath path, AddPathMode mode);
+    public object GetNativePath(IntPtr objectPointer);
 }

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

@@ -34,5 +34,6 @@ namespace PixiEditor.DrawingApi.Core.Bridge.Operations
         public void DrawImage(IntPtr objPtr, Image image, RectD rect, Paint paint);
         public void DrawBitmap(IntPtr objPtr, Bitmap bitmap, int x, int y);
         public void Dispose(IntPtr objectPointer);
+        public object GetNativeCanvas(IntPtr objectPointer);
     }
 }

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

@@ -15,5 +15,6 @@ namespace PixiEditor.DrawingApi.Core.Bridge.Operations
         public ImgData Encode(Image image, EncodedImageFormat format, int quality);
         public int GetWidth(IntPtr objectPointer);
         public int GetHeight(IntPtr objectPointer);
+        public object GetNativeImage(IntPtr objectPointer);
     }
 }

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

@@ -1,4 +1,5 @@
 using System;
+using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
@@ -15,4 +16,5 @@ public interface ISurfaceImplementation
     public DrawingSurface Create(Pixmap pixmap);
     public DrawingSurface Create(ImageInfo imageInfo);
     public void Dispose(DrawingSurface drawingSurface);
+    public object GetNativeSurface(IntPtr objectPointer);
 }

+ 3 - 1
src/PixiEditor.DrawingApi.Core/Surface/Bitmap.cs

@@ -8,7 +8,9 @@ public class Bitmap : NativeObject
     public Bitmap(IntPtr objPtr) : base(objPtr)
     {
     }
-    
+
+    public override object Native => DrawingBackendApi.Current.BitmapImplementation.GetNativeBitmap(ObjectPointer);
+
     public override void Dispose()
     {
         DrawingBackendApi.Current.BitmapImplementation.Dispose(ObjectPointer);

+ 4 - 1
src/PixiEditor.DrawingApi.Core/Surface/Canvas.cs

@@ -10,10 +10,13 @@ namespace PixiEditor.DrawingApi.Core.Surface
 {
     public class Canvas : NativeObject
     {
+
+        public override object Native => DrawingBackendApi.Current.CanvasImplementation.GetNativeCanvas(ObjectPointer);
+
         public Canvas(IntPtr objPtr) : base(objPtr)
         {
         }
-        
+
         public void DrawPixel(VecI position, Paint drawingPaint) => DrawPixel(position.X, position.Y, drawingPaint);
         public void DrawPixel(int posX, int posY, Paint drawingPaint) => 
             DrawingBackendApi.Current.CanvasImplementation.DrawPixel(ObjectPointer, posX, posY, drawingPaint);

+ 3 - 1
src/PixiEditor.DrawingApi.Core/Surface/DrawingSurface.cs

@@ -1,5 +1,6 @@
 using System;
 using PixiEditor.DrawingApi.Core.Bridge;
+using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
@@ -7,8 +8,9 @@ namespace PixiEditor.DrawingApi.Core.Surface
 {
     public class DrawingSurface : NativeObject
     {
+        public override object Native => DrawingBackendApi.Current.SurfaceImplementation.GetNativeSurface(ObjectPointer);
         public Canvas Canvas { get; private set; }
-        
+
         public DrawingSurface(IntPtr objPtr, Canvas canvas) : base(objPtr)
         {
             Canvas = canvas;

+ 2 - 0
src/PixiEditor.DrawingApi.Core/Surface/ImageData/ColorSpace.cs

@@ -5,6 +5,8 @@ namespace PixiEditor.DrawingApi.Core.Surface.ImageData;
 
 public class ColorSpace : NativeObject
 {
+    public override object Native => DrawingBackendApi.Current.ColorSpaceImplementation.GetNativeColorSpace(ObjectPointer);
+
     public ColorSpace(IntPtr objPtr) : base(objPtr)
     {
         

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

@@ -13,6 +13,8 @@ namespace PixiEditor.DrawingApi.Core.Surface.ImageData
     /// </remarks>
     public class Image : NativeObject
     {
+        public override object Native => DrawingBackendApi.Current.ImageImplementation.GetNativeImage(ObjectPointer);
+
         public int Width => DrawingBackendApi.Current.ImageImplementation.GetWidth(ObjectPointer);
         
         public int Height => DrawingBackendApi.Current.ImageImplementation.GetHeight(ObjectPointer);

+ 2 - 0
src/PixiEditor.DrawingApi.Core/Surface/ImageData/ImgData.cs

@@ -7,6 +7,8 @@ namespace PixiEditor.DrawingApi.Core.Surface.ImageData;
 /// <summary>The <see cref="ImgData" /> holds an immutable data buffer.</summary>
 public class ImgData : NativeObject
 {
+    public override object Native => DrawingBackendApi.Current.ImgDataImplementation.GetNativeImgData(ObjectPointer);
+
     public ImgData(IntPtr objPtr) : base(objPtr)
     {
     }

+ 1 - 0
src/PixiEditor.DrawingApi.Core/Surface/NativeObject.cs

@@ -4,6 +4,7 @@ namespace PixiEditor.DrawingApi.Core.Surface;
 
 public abstract class NativeObject : IDisposable
 {
+    public abstract object Native { get; }
     public IntPtr ObjectPointer { get; protected set; }
     public abstract void Dispose();
 

+ 1 - 0
src/PixiEditor.DrawingApi.Core/Surface/PaintImpl/ColorFilter.cs

@@ -6,6 +6,7 @@ namespace PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 
 public class ColorFilter : NativeObject
 {
+    public override object Native => DrawingBackendApi.Current.ColorFilterImplementation.GetNativeColorFilter(ObjectPointer);
     public ColorFilter(IntPtr objPtr) : base(objPtr)
     {
         

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

@@ -9,6 +9,8 @@ namespace PixiEditor.DrawingApi.Core.Surface.PaintImpl
     /// </summary>
     public class Paint : NativeObject
     {
+        public override object Native => DrawingBackendApi.Current.PaintImplementation.GetNativePaint(ObjectPointer);
+
         public Color Color
         {
             get => DrawingBackendApi.Current.PaintImplementation.GetColor(this);

+ 2 - 0
src/PixiEditor.DrawingApi.Core/Surface/Pixmap.cs

@@ -6,6 +6,8 @@ namespace PixiEditor.DrawingApi.Core.Surface;
 
 public class Pixmap : NativeObject
 {
+    public override object Native => DrawingBackendApi.Current.PixmapImplementation.GetNativePixmap(ObjectPointer);
+
     internal Pixmap(IntPtr objPtr) : base(objPtr)
     {
     }

+ 2 - 0
src/PixiEditor.DrawingApi.Core/Surface/Vector/VectorPath.cs

@@ -10,6 +10,8 @@ namespace PixiEditor.DrawingApi.Core.Surface.Vector;
 /// <remarks>A path encapsulates compound (multiple contour) geometric paths consisting of straight line segments, quadratic curves, and cubic curves.</remarks>
 public class VectorPath : NativeObject
 {
+    public override object Native => DrawingBackendApi.Current.PathImplementation.GetNativePath(ObjectPointer);
+
     public PathFillType FillType
     {
         get => DrawingBackendApi.Current.PathImplementation.GetFillType(this);

+ 5 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaBitmapImplementation.cs

@@ -21,5 +21,10 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             ManagedInstances[skBitmap.Handle] = skBitmap;
             return new Bitmap(skBitmap.Handle);
         }
+
+        public object GetNativeBitmap(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer];
+        }
     }
 }

+ 5 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaCanvasImplementation.cs

@@ -175,5 +175,10 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             
             ManagedInstances.TryRemove(objectPointer, out _);
         }
+
+        public object GetNativeCanvas(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer];
+        }
     }
 }

+ 5 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaColorFilterImplementation.cs

@@ -23,5 +23,10 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             skColorFilter.Dispose();
             ManagedInstances.TryRemove(skColorFilter.Handle, out _);
         }
+
+        public object GetNativeColorFilter(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer];
+        }
     }
 }

+ 5 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaColorSpaceImplementation.cs

@@ -27,5 +27,10 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             ManagedInstances[objectPointer].Dispose();
             ManagedInstances.TryRemove(objectPointer, out _);
         }
+
+        public object GetNativeColorSpace(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer];
+        }
     }
 }

+ 5 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaImageImplementation.cs

@@ -90,5 +90,10 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
         {
             return ManagedInstances[objectPointer].Height;
         }
+
+        public object GetNativeImage(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer];
+        }
     }
 }

+ 5 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaImgDataImplementation.cs

@@ -35,5 +35,10 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             SKData data = ManagedInstances[imgData.ObjectPointer];
             return data.AsSpan();
         }
+
+        public object GetNativeImgData(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer];
+        }
     }
 }

+ 5 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPaintImplementation.cs

@@ -145,5 +145,10 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             SKPaint skPaint = ManagedInstances[paint.ObjectPointer];
             skPaint.ColorFilter = colorFilterImplementation[value.ObjectPointer];
         }
+
+        public object GetNativePaint(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer];
+        }
     }
 }

+ 5 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPathImplementation.cs

@@ -147,6 +147,11 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             ManagedInstances[vectorPath.ObjectPointer].AddPath(ManagedInstances[other.ObjectPointer], (SKPathAddMode)mode);
         }
 
+        public object GetNativePath(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer];
+        }
+
         /// <summary>
         ///     Compute the result of a logical operation on two paths.
         /// </summary>

+ 5 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaPixmapImplementation.cs

@@ -54,6 +54,11 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             return ManagedInstances[pixmap.ObjectPointer].BytesSize;
         }
 
+        public object GetNativePixmap(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer];
+        }
+
         public Pixmap CreateFrom(SKPixmap pixmap)
         {
             ManagedInstances[pixmap.Handle] = pixmap;

+ 6 - 0
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaSurfaceImplementation.cs

@@ -1,5 +1,6 @@
 using System;
 using PixiEditor.DrawingApi.Core.Bridge.Operations;
+using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
@@ -71,6 +72,11 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             ManagedInstances.TryRemove(drawingSurface.ObjectPointer, out _);
         }
 
+        public object GetNativeSurface(IntPtr objectPointer)
+        {
+            return ManagedInstances[objectPointer];
+        }
+
         private DrawingSurface CreateDrawingSurface(SKSurface skSurface)
         {
             _canvasImplementation.ManagedInstances[skSurface.Canvas.Handle] = skSurface.Canvas;