Browse Source

I don't even know

Equbuxu 4 years ago
parent
commit
1effba09ae

+ 10 - 17
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -1,21 +1,19 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Windows;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Helpers;
+using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Events;
-using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
 
 namespace PixiEditor.Models.Controllers
 {
@@ -141,11 +139,6 @@ namespace PixiEditor.Models.Controllers
                     throw new InvalidOperationException($"'{SelectedTool.GetType().Name}' is either not a Tool or can't inherit '{nameof(Tool)}' directly.\nChanges the base type to either '{nameof(BitmapOperationTool)}' or '{nameof(ReadonlyTool)}'");
                 }
             }
-        }
-
-        public WriteableBitmap GetCombinedLayersBitmap()
-        {
-            return BitmapUtils.CombineLayers(ActiveDocument.Width, ActiveDocument.Height, ActiveDocument.Layers.Where(x => ActiveDocument.GetFinalLayerIsVisible(x)).ToArray(), ActiveDocument.LayerStructure);
         }
 
         /// <summary>
@@ -258,4 +251,4 @@ namespace PixiEditor.Models.Controllers
                    end.X >= 0 && end.Y >= 0;
         }
     }
-}
+}

+ 113 - 0
PixiEditor/Models/Controllers/DocumentRenderer.cs

@@ -0,0 +1,113 @@
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Layers.Utils;
+using SkiaSharp;
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class DocumentRenderer : INotifyPropertyChanged, IDisposable
+    {
+        private SKPaint BlendingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
+
+        private ObservableCollection<Layer> layers;
+        private LayerStructure structure;
+
+        private Surface finalSurface;
+        private SKSurface backingSurface;
+        private WriteableBitmap finalBitmap;
+        public WriteableBitmap FinalBitmap
+        {
+            get => finalBitmap;
+            set
+            {
+                finalBitmap = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FinalBitmap)));
+            }
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged;
+        public DocumentRenderer(ObservableCollection<Layer> layers, LayerStructure structure, int width, int height)
+        {
+            this.layers = layers;
+            this.structure = structure;
+            layers.CollectionChanged += OnLayersChanged;
+            Resize(width, height);
+        }
+
+        public void Resize(int newWidth, int newHeight)
+        {
+            finalSurface?.Dispose();
+            backingSurface?.Dispose();
+            finalSurface = new Surface(newWidth, newHeight);
+            finalBitmap = new WriteableBitmap(newWidth, newHeight, 96, 96, PixelFormats.Pbgra32, null);
+            var imageInfo = new SKImageInfo(newWidth, newHeight, SKColorType.Bgra8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb());
+            backingSurface = SKSurface.Create(imageInfo, finalBitmap.BackBuffer, finalBitmap.BackBufferStride);
+            Update(new Int32Rect(0, 0, newWidth, newHeight));
+        }
+
+        public void SetNewLayersCollection(ObservableCollection<Layer> layers)
+        {
+            layers.CollectionChanged -= OnLayersChanged;
+            this.layers = layers;
+            layers.CollectionChanged += OnLayersChanged;
+            Update(new Int32Rect(0, 0, finalSurface.Width, finalSurface.Height));
+        }
+
+        public void Dispose()
+        {
+            finalSurface.Dispose();
+            backingSurface.Dispose();
+            BlendingPaint.Dispose();
+            layers.CollectionChanged -= OnLayersChanged;
+        }
+
+        private void Update(Int32Rect dirtyRectangle)
+        {
+            finalSurface.SkiaSurface.Canvas.Clear();
+            foreach (var layer in layers)
+            {
+                BlendingPaint.Color = new SKColor(255, 255, 255, (byte)(LayerStructureUtils.GetFinalLayerOpacity(layer, structure) * 255));
+                layer.LayerBitmap.SkiaSurface.Draw(
+                    finalSurface.SkiaSurface.Canvas,
+                    layer.OffsetX,
+                    layer.OffsetY,
+                    BlendingPaint);
+            }
+            finalBitmap.Lock();
+            finalSurface.SkiaSurface.Draw(backingSurface.Canvas, 0, 0, Surface.ReplacingPaint);
+            finalBitmap.AddDirtyRect(dirtyRectangle);
+            finalBitmap.Unlock();
+        }
+
+        private void OnLayersChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+        {
+            if (e.NewItems != null)
+            {
+                foreach (var obj in e.NewItems)
+                {
+                    Layer layer = (Layer)obj;
+                    layer.LayerBitmapChanged += OnLayerBitmapChanged;
+                }
+            }
+            if (e.OldItems != null)
+            {
+                foreach (var obj in e.OldItems)
+                {
+                    ((Layer)obj).LayerBitmapChanged -= OnLayerBitmapChanged;
+                }
+            }
+            Update(new Int32Rect(0, 0, finalSurface.Width, finalSurface.Height));
+        }
+
+        private void OnLayerBitmapChanged(object sender, Int32Rect e)
+        {
+            Update(e);
+        }
+    }
+}

+ 2 - 1
PixiEditor/Models/DataHolders/Document/Document.Constructors.cs

@@ -25,6 +25,7 @@ namespace PixiEditor.Models.DataHolders
             Layers.CollectionChanged += Layers_CollectionChanged;
             LayerStructure.Groups.CollectionChanged += Groups_CollectionChanged;
             LayerStructure.LayerStructureChanged += LayerStructure_LayerStructureChanged;
+            Renderer = new DocumentRenderer(layers, layerStructure, Width, Height);
         }
 
         private void LayerStructure_LayerStructureChanged(object sender, LayerStructureChangedEventArgs e)
@@ -51,4 +52,4 @@ namespace PixiEditor.Models.DataHolders
             RaisePropertyChanged(nameof(Layers));
         }
     }
-}
+}

+ 15 - 4
PixiEditor/Models/DataHolders/Document/Document.Layers.cs

@@ -13,7 +13,6 @@ using System.Linq;
 using System.Text.RegularExpressions;
 using System.Windows;
 using System.Windows.Media;
-using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.DataHolders
 {
@@ -34,19 +33,31 @@ namespace PixiEditor.Models.DataHolders
             {
                 layers = value;
                 Layers.CollectionChanged += Layers_CollectionChanged;
+                Renderer.SetNewLayersCollection(value);
             }
         }
 
         public LayerStructure LayerStructure
         {
             get => layerStructure;
-            set
+            private set
             {
                 layerStructure = value;
                 RaisePropertyChanged(nameof(LayerStructure));
             }
         }
 
+        private DocumentRenderer renderer;
+        public DocumentRenderer Renderer
+        {
+            get => renderer;
+            private set
+            {
+                renderer = value;
+                RaisePropertyChanged(nameof(Renderer));
+            }
+        }
+
         public Layer ActiveLayer => Layers.Count > 0 ? Layers.FirstOrDefault(x => x.LayerGuid == ActiveLayerGuid) : null;
 
         public Guid ActiveLayerGuid
@@ -190,9 +201,9 @@ namespace PixiEditor.Models.DataHolders
             UndoManager.SquashUndoChanges(2, "Move group");
         }
 
-        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
+        public void AddNewLayer(string name, Surface bitmap, bool setAsActive = true)
         {
-            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
+            AddNewLayer(name, bitmap.Width, bitmap.Height, setAsActive);
             Layers.Last().LayerBitmap = bitmap;
         }
 

+ 4 - 5
PixiEditor/Models/DataHolders/Document/Document.cs

@@ -14,7 +14,6 @@ using System.IO;
 using System.Linq;
 using System.Windows;
 using System.Windows.Media;
-using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.DataHolders
 {
@@ -47,22 +46,22 @@ namespace PixiEditor.Models.DataHolders
                 + (!ChangesSaved ? " *" : string.Empty);
         }
 
-        private int width;
+        private int width = 1;
         public int Width
         {
             get => width;
-            set
+            private set
             {
                 width = value;
                 RaisePropertyChanged("Width");
             }
         }
 
-        private int height;
+        private int height = 1;
         public int Height
         {
             get => height;
-            set
+            private set
             {
                 height = value;
                 RaisePropertyChanged("Height");

+ 46 - 10
PixiEditor/Models/DataHolders/Surface.cs

@@ -10,6 +10,7 @@ namespace PixiEditor.Models.DataHolders
     public class Surface : IDisposable
     {
         public static SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
+
         private static readonly SKPaint nearestNeighborReplacingPaint = new SKPaint() { BlendMode = SKBlendMode.Src, FilterQuality = SKFilterQuality.Low };
 
         public SKSurface SkiaSurface { get; }
@@ -46,6 +47,8 @@ namespace PixiEditor.Models.DataHolders
                 var newSurface = CreateSurface(w, h);
                 surface.Draw(newSurface.Canvas, 0, 0, ReplacingPaint);
                 SkiaSurface = newSurface;
+                Width = w;
+                Height = h;
             }
             finally
             {
@@ -61,9 +64,13 @@ namespace PixiEditor.Models.DataHolders
             return newSurface;
         }
 
-        /// <summary>
-        /// probably doesn't work correctly
-        /// </summary>
+        public Surface Crop(int x, int y, int width, int height)
+        {
+            Surface result = new Surface(width, height);
+            SkiaSurface.Draw(result.SkiaSurface.Canvas, x, y, ReplacingPaint);
+            return result;
+        }
+
         public SKColor GetSRGBPixel(int x, int y)
         {
             var imageInfo = new SKImageInfo(1, 1, SKColorType.Bgra8888, SKAlphaType.Premul);
@@ -72,6 +79,24 @@ namespace PixiEditor.Models.DataHolders
             SkiaSurface.ReadPixels(imageInfo, dstpixels, imageInfo.RowBytes, x, y);
             return bitmap.GetPixel(0, 0);
         }
+        /*
+        public FloatColor GetFloatPixel(int x, int y)
+        {
+            var imageInfo = new SKImageInfo(1, 1, SKColorType.RgbaF32, SKAlphaType.Unpremul);
+            var buffer = Marshal.AllocHGlobal(16);
+            try
+            {
+                using SKSurface dstSurface = SKSurface.Create(imageInfo, buffer, 16);
+                SkiaSurface.Draw(dstSurface.Canvas, -x, -y, ReplacingPaint);
+                float[] output = new float[4];
+                Marshal.Copy(buffer, output, 0, 4);
+                return new FloatColor(output[0], output[1], output[2], output[3]);
+            }
+            finally
+            {
+                Marshal.FreeHGlobal(buffer);
+            }
+        }*/
 
         public void SetSRGBPixel(int x, int y, SKColor color)
         {
@@ -79,20 +104,31 @@ namespace PixiEditor.Models.DataHolders
             SkiaSurface.Canvas.DrawPoint(x, y, drawingPaint);
         }
 
-        /// <summary>
-        /// probably doesn't work correctly
-        /// </summary>
         public byte[] ToPbgra32ByteArray()
         {
-            return SkiaSurface.Snapshot().Encode(SKEncodedImageFormat.Bmp, 100).ToArray();
+            var imageInfo = new SKImageInfo(Width, Height, SKColorType.Bgra8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb());
+            var buffer = Marshal.AllocHGlobal(Width * Height * 4);
+            try
+            {
+                using SKSurface surface = SKSurface.Create(imageInfo, buffer, Width * 4);
+                SkiaSurface.Draw(surface.Canvas, 0, 0, ReplacingPaint);
+                byte[] managed = new byte[Width * Height * 4];
+                Marshal.Copy(buffer, managed, 0, Width * Height * 4);
+                return managed;
+            }
+            finally
+            {
+                Marshal.FreeHGlobal(buffer);
+            }
         }
 
         public WriteableBitmap ToWriteableBitmap()
         {
             WriteableBitmap result = new WriteableBitmap(Width, Height, 96, 96, PixelFormats.Pbgra32, null);
             result.Lock();
-            result.CopyPixels(ToPbgra32ByteArray(), Width * 4, 0);
-            result.AddDirtyRect(new Int32Rect(0, 0, Width, Height));
+            var dirty = new Int32Rect(0, 0, Width, Height);
+            result.WritePixels(dirty, ToPbgra32ByteArray(), Width * 4, 0);
+            result.AddDirtyRect(dirty);
             result.Unlock();
             return result;
         }
@@ -104,7 +140,7 @@ namespace PixiEditor.Models.DataHolders
 
         private static SKSurface CreateSurface(int w, int h)
         {
-            return SKSurface.Create(new SKImageInfo(0, 0, SKColorType.RgbaF16, SKAlphaType.Premul, SKColorSpace.CreateSrgb()));
+            return SKSurface.Create(new SKImageInfo(w, h, SKColorType.RgbaF16, SKAlphaType.Premul, SKColorSpace.CreateSrgb()));
         }
 
     }

+ 32 - 7
PixiEditor/Models/IO/Exporter.cs

@@ -1,11 +1,14 @@
-using System;
-using System.IO;
-using System.Windows;
-using System.Windows.Media.Imaging;
-using Microsoft.Win32;
+using Microsoft.Win32;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
+using SkiaSharp;
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.IO
 {
@@ -75,7 +78,7 @@ namespace PixiEditor.Models.IO
         /// <param name="exportWidth">File width.</param>
         /// <param name="exportHeight">File height.</param>
         /// <param name="bitmap">Bitmap to save.</param>
-        public static void SaveAsPng(string savePath, int exportWidth, int exportHeight, WriteableBitmap bitmap)
+        private static void SaveAsPng(string savePath, int exportWidth, int exportHeight, WriteableBitmap bitmap)
         {
             try
             {
@@ -92,5 +95,27 @@ namespace PixiEditor.Models.IO
                 MessageBox.Show(err.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
             }
         }
+
+        public static void SaveAsGZippedBytes(string path, Surface surface)
+        {
+            var imageInfo = new SKImageInfo(surface.Width, surface.Height, SKColorType.RgbaF16);
+            var unmanagedBuffer = Marshal.AllocHGlobal(surface.Width * surface.Height * 8);
+            //+8 bytes for width and height
+            var bytes = new byte[surface.Width * surface.Height * 8 + 8];
+            try
+            {
+                surface.SkiaSurface.ReadPixels(imageInfo, unmanagedBuffer, surface.Width * 8, 0, 0);
+                Marshal.Copy(unmanagedBuffer, bytes, 8, surface.Width * surface.Height * 8);
+            }
+            finally
+            {
+                Marshal.FreeHGlobal(unmanagedBuffer);
+            }
+            BitConverter.GetBytes((int)surface.Width).CopyTo(bytes, 0);
+            BitConverter.GetBytes((int)surface.Height).CopyTo(bytes, 4);
+            using FileStream outputStream = new(path, FileMode.Create);
+            using GZipStream compressedStream = new GZipStream(outputStream, CompressionLevel.Fastest);
+            compressedStream.Write(bytes);
+        }
     }
-}
+}

+ 43 - 11
PixiEditor/Models/IO/Importer.cs

@@ -1,12 +1,14 @@
-using System;
-using System.IO;
-using System.Runtime.Serialization;
-using System.Windows.Media.Imaging;
-using PixiEditor.Exceptions;
+using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Parser;
+using SkiaSharp;
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.InteropServices;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.IO
 {
@@ -19,12 +21,14 @@ namespace PixiEditor.Models.IO
         /// <param name="width">New width of image.</param>
         /// <param name="height">New height of image.</param>
         /// <returns>WriteableBitmap of imported image.</returns>
-        public static WriteableBitmap ImportImage(string path, int width, int height)
+        public static Surface ImportImage(string path, int width, int height)
         {
-            WriteableBitmap wbmp = ImportImage(path);
-            if (wbmp.PixelWidth != width || wbmp.PixelHeight != height)
+            Surface wbmp = ImportImage(path);
+            if (wbmp.Width != width || wbmp.Height != height)
             {
-                return wbmp.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+                var resized = wbmp.ResizeNearestNeighbor(width, height);
+                wbmp.Dispose();
+                return resized;
             }
 
             return wbmp;
@@ -34,7 +38,7 @@ namespace PixiEditor.Models.IO
         ///     Imports image from path and resizes it to given dimensions.
         /// </summary>
         /// <param name="path">Path of image.</param>
-        public static WriteableBitmap ImportImage(string path)
+        public static Surface ImportImage(string path)
         {
             try
             {
@@ -76,5 +80,33 @@ namespace PixiEditor.Models.IO
             path = path.ToLower();
             return path.EndsWith(".pixi") || path.EndsWith(".png") || path.EndsWith(".jpg") || path.EndsWith(".jpeg");
         }
+
+        public static Surface LoadFromGZippedBytes(string path)
+        {
+            using FileStream compressedData = new(path, FileMode.Open);
+            using GZipStream uncompressedData = new(compressedData, CompressionMode.Decompress);
+            using MemoryStream resultBytes = new();
+            uncompressedData.CopyTo(resultBytes);
+
+            byte[] bytes = resultBytes.ToArray();
+            int width = BitConverter.ToInt32(bytes, 0);
+            int height = BitConverter.ToInt32(bytes, 4);
+
+            SKImageInfo info = new SKImageInfo(width, height, SKColorType.RgbaF16);
+            var ptr = Marshal.AllocHGlobal(bytes.Length - 8);
+            try
+            {
+                Marshal.Copy(bytes, 8, ptr, bytes.Length - 8);
+                SKPixmap map = new(info, ptr);
+                SKSurface surface = SKSurface.Create(map);
+                var finalSurface = new Surface(width, height);
+                surface.Draw(finalSurface.SkiaSurface.Canvas, 0, 0, Surface.ReplacingPaint);
+                return finalSurface;
+            }
+            finally
+            {
+                Marshal.FreeHGlobal(ptr);
+            }
+        }
     }
-}
+}

+ 0 - 25
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -15,31 +15,6 @@ namespace PixiEditor.Models.ImageManipulation
 {
     public static class BitmapUtils
     {
-        /// <summary>
-        ///     Converts pixel bytes to WriteableBitmap.
-        /// </summary>
-        /// <param name="currentBitmapWidth">Width of bitmap.</param>
-        /// <param name="currentBitmapHeight">Height of bitmap.</param>
-        /// <param name="byteArray">Bitmap byte array.</param>
-        /// <returns>WriteableBitmap.</returns>
-        public static WriteableBitmap BytesToWriteableBitmap(int currentBitmapWidth, int currentBitmapHeight, byte[] byteArray)
-        {
-            WriteableBitmap bitmap = BitmapFactory.New(currentBitmapWidth, currentBitmapHeight);
-            if (byteArray != null)
-            {
-                bitmap.FromByteArray(byteArray);
-            }
-
-            return bitmap;
-        }
-
-        /// <summary>
-        ///     Converts layers bitmaps into one bitmap.
-        /// </summary>
-        /// <param name="width">Width of final bitmap.</param>
-        /// <param name="height">Height of final bitmap.</param>.
-        /// <param name="layers">Layers to combine.</param>
-        /// <returns>WriteableBitmap of layered bitmaps.</returns>
         public static Surface CombineLayers(int width, int height, IEnumerable<Layer> layers, LayerStructure structure = null)
         {
             Surface finalSurface = new(width, height);

+ 8 - 0
PixiEditor/Models/Layers/Layer.cs

@@ -198,6 +198,14 @@ namespace PixiEditor.Models.Layers
 
         public int MaxHeight { get; set; } = int.MaxValue;
 
+        public event EventHandler<Int32Rect> LayerBitmapChanged;
+
+        public void InvokeLayerBitmapChange(Int32Rect dirtyArea)
+        {
+            LayerBitmapChanged?.Invoke(this, dirtyArea);
+        }
+
+
         /// <summary>
         /// Changes Guid of layer.
         /// </summary>

+ 6 - 7
PixiEditor/Models/Layers/LayerHelper.cs

@@ -1,10 +1,9 @@
-using System;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media.Imaging;
-using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.ImageManipulation;
 using PixiEditor.ViewModels;
+using System;
+using System.Linq;
+using System.Windows;
 
 namespace PixiEditor.Models.Layers
 {
@@ -70,7 +69,7 @@ namespace PixiEditor.Models.Layers
             int height = yCloser.Height + offsetY + yOther.Height;
 
             // Merge both layers into a bitmap
-            WriteableBitmap mergedBitmap = BitmapUtils.CombineLayers((int)documentsSize.X, (int)documentsSize.Y, new Layer[] { thisLayer, otherLayer });
+            Surface mergedBitmap = BitmapUtils.CombineLayers((int)documentsSize.X, (int)documentsSize.Y, new Layer[] { thisLayer, otherLayer });
             mergedBitmap = mergedBitmap.Crop(xCloser.OffsetX, yCloser.OffsetY, width, height);
 
             // Create the new layer with the merged bitmap
@@ -87,4 +86,4 @@ namespace PixiEditor.Models.Layers
             return MergeWith(thisLayer, otherLayer, newName, new Vector(documentWidth, documentHeight));
         }
     }
-}
+}

+ 2 - 2
PixiEditor/Models/Undo/StorageBasedChange.cs

@@ -58,7 +58,7 @@ namespace PixiEditor.Models.Undo
                 UndoLayer storedLayer = StoredLayers[i];
                 if (Directory.Exists(Path.GetDirectoryName(storedLayer.StoredPngLayerName)))
                 {
-                    Exporter.SaveAsPng(storedLayer.StoredPngLayerName, storedLayer.Width, storedLayer.Height, layer.LayerBitmap);
+                    Exporter.SaveAsGZippedBytes(storedLayer.StoredPngLayerName, layer.LayerBitmap);
                 }
 
                 i++;
@@ -77,7 +77,7 @@ namespace PixiEditor.Models.Undo
             for (int i = 0; i < StoredLayers.Length; i++)
             {
                 UndoLayer storedLayer = StoredLayers[i];
-                var bitmap = Importer.ImportImage(storedLayer.StoredPngLayerName, storedLayer.Width, storedLayer.Height);
+                var bitmap = Importer.LoadFromGZippedBytes(storedLayer.StoredPngLayerName);
                 layers[i] = new Layer(storedLayer.Name, bitmap)
                 {
                     Offset = new System.Windows.Thickness(storedLayer.OffsetX, storedLayer.OffsetY, 0, 0),

+ 9 - 10
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -1,11 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.IO;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media.Imaging;
-using Microsoft.Win32;
+using Microsoft.Win32;
 using Newtonsoft.Json.Linq;
 using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
@@ -15,6 +8,12 @@ using PixiEditor.Models.IO;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.Parser;
 using PixiEditor.Views.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -262,7 +261,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// <param name="parameter">CommandProperty.</param>
         private void ExportFile(object parameter)
         {
-            WriteableBitmap bitmap = Owner.BitmapManager.GetCombinedLayersBitmap();
+            WriteableBitmap bitmap = Owner.BitmapManager.ActiveDocument.Renderer.FinalBitmap;
             Exporter.Export(bitmap, new Size(bitmap.PixelWidth, bitmap.PixelHeight));
         }
 
@@ -310,4 +309,4 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             return documents;
         }
     }
-}
+}

+ 90 - 1
PixiEditorTests/ModelsTests/DataHoldersTests/SurfaceTests.cs

@@ -1,6 +1,95 @@
-namespace PixiEditorTests.ModelsTests.DataHoldersTests
+using PixiEditor.Models.DataHolders;
+using SkiaSharp;
+using Xunit;
+
+namespace PixiEditorSkiaRewrite
 {
     public class SurfaceTests
     {
+        SKColor redColor = new SKColor(254, 2, 3);
+        SKColor greenColor = new SKColor(6, 224, 3);
+        SKPaint redPaint;
+        SKPaint greenPaint;
+
+        public SurfaceTests()
+        {
+            redPaint = new SKPaint()
+            {
+                Color = redColor,
+            };
+            greenPaint = new SKPaint()
+            {
+                Color = greenColor,
+            };
+        }
+
+        [Fact]
+        public void TestSurfaceSRGBPixelManipulation()
+        {
+            using Surface surface = new Surface(128, 200);
+            surface.SkiaSurface.Canvas.Clear(SKColors.Red);
+            surface.SkiaSurface.Canvas.DrawRect(new SKRect(10, 10, 70, 70), redPaint);
+            surface.SetSRGBPixel(73, 21, greenColor);
+            Assert.Equal(redColor, surface.GetSRGBPixel(14, 14));
+            Assert.Equal(greenColor, surface.GetSRGBPixel(73, 21));
+        }
+
+        [Fact]
+        public void TestSurfacePbgraBytes()
+        {
+            byte[] bytes = new byte[]
+            {
+                123, 121, 141, 255,  014, 010, 007, 255,
+                042, 022, 055, 128,  024, 020, 021, 128,
+                040, 010, 055, 064,  042, 022, 005, 064,
+                005, 009, 001, 032,  001, 011, 016, 032,
+            };
+            using Surface surface = new Surface(2, 4, bytes);
+            Assert.Equal(new SKColor(141, 121, 123, 255), surface.GetSRGBPixel(0, 0));
+            Assert.Equal(new SKColor(110, 44, 84, 128), surface.GetSRGBPixel(0, 1));
+            var newBytes = surface.ToPbgra32ByteArray();
+            Assert.Equal(bytes, newBytes);
+        }
+
+        [Fact]
+        public void TestCloneSurface()
+        {
+            using Surface original = new Surface(30, 40);
+            original.SkiaSurface.Canvas.Clear(redColor);
+            original.SkiaSurface.Canvas.DrawRect(5, 5, 10, 10, greenPaint);
+            using Surface clone = new Surface(original);
+            Assert.NotSame(original.SkiaSurface, clone.SkiaSurface);
+            Assert.NotSame(original.SkiaSurface.Canvas, clone.SkiaSurface.Canvas);
+            Assert.Equal(redColor, clone.GetSRGBPixel(3, 3));
+            Assert.Equal(greenColor, clone.GetSRGBPixel(6, 6));
+        }
+
+        [Fact]
+        public void TestSurfaceNearestNeighborResize()
+        {
+            using Surface original = new Surface(30, 40);
+            original.SkiaSurface.Canvas.Clear(redColor);
+            original.SkiaSurface.Canvas.DrawRect(5, 5, 20, 20, greenPaint);
+            using Surface resized = original.ResizeNearestNeighbor(10, 10);
+            Assert.Equal(10, resized.Width);
+            Assert.Equal(10, resized.Height);
+            Assert.Equal(redColor, resized.GetSRGBPixel(0, 0));
+            Assert.Equal(redColor, resized.GetSRGBPixel(9, 9));
+            Assert.Equal(greenColor, resized.GetSRGBPixel(5, 5));
+        }
+
+        [Fact]
+        public void TestSurfaceToWriteableBitmap()
+        {
+            using Surface original = new Surface(30, 40);
+            original.SkiaSurface.Canvas.Clear(redColor);
+            original.SkiaSurface.Canvas.DrawRect(5, 5, 20, 20, greenPaint);
+            var bitmap = original.ToWriteableBitmap();
+            byte[] pixels = new byte[30 * 40 * 4];
+            bitmap.CopyPixels(pixels, 30 * 4, 0);
+            Assert.Equal(redColor, new SKColor(pixels[2], pixels[1], pixels[0], pixels[3]));
+            int offset = (30 * 5 + 5) * 4;
+            Assert.Equal(greenColor, new SKColor(pixels[2 + offset], pixels[1 + offset], pixels[0 + offset], pixels[3 + offset]));
+        }
     }
 }

+ 25 - 4
PixiEditorTests/ModelsTests/IO/ImporterTests.cs

@@ -1,8 +1,11 @@
-using System;
+using PixiEditor.Exceptions;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.IO;
+using SkiaSharp;
+using System;
+using System.IO;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
-using PixiEditor.Exceptions;
-using PixiEditor.Models.IO;
 using Xunit;
 
 namespace PixiEditorTests.ModelsTests.IO
@@ -70,5 +73,23 @@ namespace PixiEditorTests.ModelsTests.IO
             Assert.Equal(10, image.PixelWidth);
             Assert.Equal(10, image.PixelHeight);
         }
+
+        [Fact]
+        public void TestSaveAndLoadGZippedBytes()
+        {
+            using Surface original = new Surface(123, 456);
+            original.SkiaSurface.Canvas.Clear(SKColors.Red);
+            using SKPaint paint = new SKPaint();
+            paint.BlendMode = SKBlendMode.Src;
+            paint.Color = new SKColor(128, 64, 32, 16);
+            original.SkiaSurface.Canvas.DrawRect(10, 10, 20, 20, paint);
+            Exporter.SaveAsGZippedBytes("pleasedontoverwritethings", original);
+            using var loaded = Importer.LoadFromGZippedBytes("pleasedontoverwritethings");
+            File.Delete("pleasedontoverwritethings");
+            Assert.Equal(original.Width, loaded.Width);
+            Assert.Equal(original.Height, loaded.Height);
+            Assert.Equal(original.GetSRGBPixel(0, 0), loaded.GetSRGBPixel(0, 0));
+            Assert.Equal(original.GetSRGBPixel(15, 15), loaded.GetSRGBPixel(15, 15));
+        }
     }
-}
+}