Browse Source

Clipboard WIP

CPKreuz 3 years ago
parent
commit
7fcab5793d

+ 25 - 0
PixiEditor/Helpers/Extensions/Int32RectHelpers.cs

@@ -0,0 +1,25 @@
+using System.Windows;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class Int32RectHelpers
+    {
+        public static Int32Rect Min(this Int32Rect rect, Int32Rect other)
+        {
+            int width = rect.Width;
+            int height = rect.Height;
+
+            if (width + rect.X > other.Width + other.X)
+            {
+                width = other.Width;
+            }
+
+            if (height + rect.Y > other.Height + other.Y)
+            {
+                height = other.Height;
+            }
+
+            return new Int32Rect(rect.X, rect.Y, width, height);
+        }
+    }
+}

+ 109 - 90
PixiEditor/Helpers/Extensions/ParserHelpers.cs

@@ -1,9 +1,11 @@
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
-using PixiEditor.Parser;
-using PixiEditor.Parser.Models;
+using PixiEditor.Parser;
+using PixiEditor.Parser.Collections;
+using PixiEditor.Parser.Skia;
 using SkiaSharp;
 using System;
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Drawing;
 using System.Linq;
@@ -13,15 +15,15 @@ namespace PixiEditor.Helpers.Extensions
 {
     public static class ParserHelpers
     {
-        public static Document ToDocument(this Parser.SerializableDocument serializableDocument)
+        public static Document ToDocument(this SerializableDocument serializableDocument)
         {
             Document document = new Document(serializableDocument.Width, serializableDocument.Height)
             {
                 Layers = serializableDocument.ToLayers(),
-                Swatches = new ObservableCollection<SKColor>(serializableDocument.Swatches.Select(x => new SKColor(x.R, x.G, x.B, x.A)))
+                Swatches = new ObservableCollection<SKColor>(serializableDocument.Swatches.ToSKColors())
             };
 
-            document.LayerStructure.Groups = serializableDocument.ToGroups();
+            document.LayerStructure.Groups = serializableDocument.ToGroups(document);
 
             if (document.Layers.Count > 0)
             {
@@ -31,113 +33,130 @@ namespace PixiEditor.Helpers.Extensions
             return document;
         }
 
-        public static ObservableCollection<GuidStructureItem> ToGroups(this SerializableDocument serializableDocument)
+        public static ObservableCollection<Layer> ToLayers(this SerializableDocument document)
         {
-            return ToGroups(serializableDocument.Groups);
+            ObservableCollection<Layer> layers = new();
+
+            foreach (SerializableLayer slayer in document)
+            {
+                layers.Add(slayer.ToLayer());
+            }
+
+            return layers;
         }
 
-        public static ObservableCollection<Layer> ToLayers(this SerializableDocument serializableDocument)
-        {
-            ObservableCollection<Layer> layers = new ObservableCollection<Layer>();
-            for (int i = 0; i < serializableDocument.Layers.Count; i++)
-            {
-                Parser.SerializableLayer serLayer = serializableDocument.Layers[i];
-                Layer layer =
-                    new Layer(serLayer.Name, new Surface(serLayer.Width, serLayer.Height, serLayer.BitmapBytes))
-                    {
-                        IsVisible = serLayer.IsVisible,
-                        Offset = new Thickness(serLayer.OffsetX, serLayer.OffsetY, 0, 0),
-                        Opacity = serLayer.Opacity,
-                        MaxHeight = serializableDocument.Height,
-                        MaxWidth = serializableDocument.Width,
-                    };
-                if (serLayer.LayerGuid != Guid.Empty)
-                {
-                    layer.ChangeGuid(serLayer.LayerGuid);
-                }
-                layers.Add(layer);
-            }
+        public static Layer ToLayer(this SerializableLayer layer)
+        {
+            return new Layer(layer.Name, new Surface(layer.ToSKImage()))
+            {
+                Opacity = layer.Opacity,
+                IsVisible = layer.IsVisible,
+                Offset = new(layer.OffsetX, layer.OffsetY, 0, 0)
+            };
+        }
 
-            return layers;
+        public static ObservableCollection<GuidStructureItem> ToGroups(this SerializableDocument sdocument, Document document)
+        {
+            ObservableCollection<GuidStructureItem> groups = new();
+
+            if (sdocument.Groups == null)
+            {
+                return groups;
+            }
+
+            foreach (SerializableGroup sgroup in sdocument.Groups)
+            {
+                groups.Add(sgroup.ToGroup(null, document));
+            }
+
+            return groups;
         }
 
-        public static SerializableDocument ToSerializable(this Document document)
-        {
-            Parser.SerializableDocument serializable = new Parser.SerializableDocument
-            {
-                Width = document.Width,
-                Height = document.Height,
-                Layers = document.Layers.Select(x => x.ToSerializable()).ToList(),
-                Groups = document.LayerStructure.Groups.Select(x => x.ToSerializable()).ToArray(),
-                Swatches = document.Swatches.Select(x => Color.FromArgb(x.Alpha, x.Red, x.Green, x.Blue)).ToList()
-            };
+        public static GuidStructureItem ToGroup(this SerializableGroup sgroup, GuidStructureItem parent, Document document)
+        {
+            GuidStructureItem group = new GuidStructureItem(sgroup.Name, Guid.Empty)
+            {
+                Opacity = sgroup.Opacity,
+                IsVisible = sgroup.IsVisible,
+                Parent = parent,
+                StartLayerGuid = document.Layers[sgroup.StartLayer].LayerGuid,
+                EndLayerGuid = document.Layers[sgroup.EndLayer].LayerGuid
+            };
+
+            group.Subgroups = new(sgroup.Subgroups.ToGroups(document, group));
+
+            return group;
+        }
 
-            return serializable;
+        public static SerializableDocument ToSerializable(this Document document)
+        {
+            return new SerializableDocument(document.Width, document.Height,
+                                            document.LayerStructure.Groups.ToSerializable(document),
+                                            document.Layers.ToSerializable()).AddSwatches(document.Swatches);
         }
 
-        public static SerializableGuidStructureItem ToSerializable(this GuidStructureItem group, SerializableGuidStructureItem parent = null)
-        {
-            var serializedGroup = new SerializableGuidStructureItem(
-                    group.GroupGuid,
-                    group.Name,
-                    group.StartLayerGuid,
-                    group.EndLayerGuid,
-                    null, group.IsVisible, group.Opacity);
-            serializedGroup.Subgroups = group.Subgroups.Select(x => x.ToSerializable(serializedGroup)).ToArray();
-            return serializedGroup;
+        public static IEnumerable<SerializableLayer> ToSerializable(this IEnumerable<Layer> layers)
+        {
+            foreach (Layer layer in layers)
+            {
+                yield return layer.ToSerializable();
+            }
         }
 
-        public static Parser.SerializableLayer ToSerializable(this Layer layer)
-        {
-            Parser.SerializableLayer serializable = new Parser.SerializableLayer
-            {
-                LayerGuid = layer.LayerGuid,
-                Name = layer.Name,
-                Width = layer.Width,
-                Height = layer.Height,
-                BitmapBytes = layer.ConvertBitmapToBytes(),
-                IsVisible = layer.IsVisible,
-                OffsetX = (int)layer.Offset.Left,
-                OffsetY = (int)layer.Offset.Top,
-                Opacity = layer.Opacity,
-                MaxWidth = layer.MaxWidth,
-                MaxHeight = layer.MaxHeight
-            };
+        public static SerializableLayer ToSerializable(this Layer layer)
+        {
+            return new SerializableLayer(layer.Width, layer.Height, layer.OffsetX, layer.OffsetY)
+            {
+                IsVisible = layer.IsVisible,
+                Opacity = layer.Opacity,
+                Name = layer.Name
+            }.FromSKImage(layer.LayerBitmap.SkiaSurface.Snapshot());
+        }
 
-            return serializable;
-        }
-
-        private static ObservableCollection<GuidStructureItem> ToGroups(SerializableGuidStructureItem[] serializableGroups, GuidStructureItem parent = null)
+        public static IEnumerable<SerializableGroup> ToSerializable(this IEnumerable<GuidStructureItem> groups, Document document)
         {
-            ObservableCollection<GuidStructureItem> groups = new ObservableCollection<GuidStructureItem>();
+            foreach (GuidStructureItem group in groups)
+            {
+                yield return group.ToSerializable(document);
+            }
+        }
+
+        public static SerializableGroup ToSerializable(this GuidStructureItem group, Document document)
+        {
+            SerializableGroup serializable = new SerializableGroup(group.Name, group.Subgroups.ToSerializable(document))
+            {
+                Opacity = group.Opacity,
+                IsVisible = group.IsVisible
+            };
 
-            if (serializableGroups == null)
+            for (int i = 0; i < document.Layers.Count; i++)
             {
-                return groups;
+                if (group.StartLayerGuid == document.Layers[i].LayerGuid)
+                {
+                    serializable.StartLayer = i;
+                }
+
+                if (group.EndLayerGuid == document.Layers[i].LayerGuid)
+                {
+                    serializable.EndLayer = i;
+                }
             }
 
-            foreach (var serializableGroup in serializableGroups)
+            return serializable;
+        }
+
+        private static IEnumerable<GuidStructureItem> ToGroups(this IEnumerable<SerializableGroup> groups, Document document, GuidStructureItem parent)
+        {
+            foreach (SerializableGroup sgroup in groups)
             {
-                groups.Add(ToGroup(serializableGroup, parent));
+                yield return sgroup.ToGroup(parent, document);
             }
-            return groups;
         }
 
-        private static GuidStructureItem ToGroup(SerializableGuidStructureItem group, GuidStructureItem parent = null)
+        private static SerializableDocument AddSwatches(this SerializableDocument document, IEnumerable<SKColor> colors)
         {
-            if (group == null)
-            {
-                return null;
-            }
-            var parsedGroup = new GuidStructureItem(
-                group.Name,
-                group.StartLayerGuid,
-                group.EndLayerGuid,
-                new ObservableCollection<GuidStructureItem>(),
-                parent)
-            { Opacity = group.Opacity, IsVisible = group.IsVisible, GroupGuid = group.GroupGuid, IsExpanded = true };
-            parsedGroup.Subgroups = ToGroups(group.Subgroups, parsedGroup);
-            return parsedGroup;
+            document.Swatches.AddRange(colors);
+            return document;
         }
     }
 }

+ 163 - 38
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -1,41 +1,88 @@
-using PixiEditor.Models.DataHolders;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.IO;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Undo;
+using PixiEditor.Parser;
+using PixiEditor.Parser.Skia;
 using PixiEditor.ViewModels;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.IO;
 using System.Linq;
 using System.Windows;
+using System.Windows.Media;
 using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.Controllers
 {
     public static class ClipboardController
     {
+        public static readonly string TempCopyFilePath = Path.Join(
+                    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+                    "PixiEditor",
+                    "Copied.png");
+
         /// <summary>
-        ///     Copies selection to clipboard in PNG, Bitmap and DIB formats.
+        ///     Copies the selection to clipboard in PNG, Bitmap and DIB formats.
         /// </summary>
         /// <param name="layers">Layers where selection is.</param>
-        public static void CopyToClipboard(Layer[] layers, Coordinates[] selection, int originalImageWidth, int originalImageHeight)
+        public static void CopyToClipboard(Layer[] layers, Coordinates[] selection, int originalImageWidth, int originalImageHeight, SerializableDocument document = null)
         {
             Clipboard.Clear();
-            using var tempSurface = BitmapUtils.CombineLayers(originalImageWidth, originalImageHeight, layers);
-            WriteableBitmap combinedBitmaps = tempSurface.ToWriteableBitmap();
-            using (MemoryStream pngStream = new MemoryStream())
+            using Surface surface = BitmapUtils.CombineLayers(originalImageWidth, originalImageHeight, layers);
+            DataObject data = new DataObject();
+
+            WriteableBitmap combinedBitmaps = surface.ToWriteableBitmap();
+            BitmapSource croppedBmp = BitmapSelectionToBmpSource(combinedBitmaps, selection, out int offsetX, out int offsetY, out int width, out int height);
+            data.SetData(typeof(CropData), new CropData(width, height, offsetX, offsetY).ToStream());
+
+            using (SKData pngData = surface.SkiaSurface.Snapshot(SKRectI.Create(offsetX, offsetY, width, height)).Encode())
             {
-                DataObject data = new DataObject();
-                BitmapSource croppedBmp = BitmapSelectionToBmpSource(combinedBitmaps, selection);
-                data.SetData(DataFormats.Bitmap, croppedBmp, true); // Bitmap, no transparency support
+                // Stream should not be disposed
+                MemoryStream pngStream = new MemoryStream();
+                pngData.AsStream().CopyTo(pngStream);
 
-                PngBitmapEncoder encoder = new PngBitmapEncoder();
-                encoder.Frames.Add(BitmapFrame.Create(croppedBmp));
-                encoder.Save(pngStream);
                 data.SetData("PNG", pngStream, false); // PNG, supports transparency
 
-                Clipboard.SetImage(croppedBmp); // DIB format
+                pngStream.Position = 0;
+                Directory.CreateDirectory(Path.GetDirectoryName(TempCopyFilePath));
+                using FileStream fileStream = new FileStream(TempCopyFilePath, FileMode.Create, FileAccess.Write);
+                pngStream.CopyTo(fileStream);
+                data.SetFileDropList(new StringCollection() { TempCopyFilePath });
+            }
+
+            data.SetData(DataFormats.Bitmap, croppedBmp, true); // Bitmap, no transparency
+            data.SetImage(croppedBmp); // DIB format, no transparency
+
+            if (document != null)
+            {
+                MemoryStream memoryStream = new();
+                PixiParser.Serialize(document, memoryStream);
+                data.SetData("PIXI", memoryStream); // PIXI, supports transparency, layers, groups and swatches
                 Clipboard.SetDataObject(data, true);
             }
+
+            Clipboard.SetDataObject(data, true);
+        }
+
+        /// <summary>
+        /// Copies the selection to clipboard in PNG, Bitmap and DIB formats. <para/>
+        /// Also serailizes the <paramref name="document"/> in the PIXI format and copies it to the clipboard.
+        /// </summary>
+        public static void CopyToClipboard(Document document)
+        {
+            CopyToClipboard(
+                document.Layers.Where(x => document.GetFinalLayerIsVisible(x)).ToArray(),
+                //doc.ActiveSelection.SelectedPoints.ToArray(),
+                new Coordinates[] { (0, 0), (15, 15) },
+                document.Width,
+                document.Height,
+                document.ToSerializable());
         }
 
         /// <summary>
@@ -43,13 +90,14 @@ namespace PixiEditor.Models.Controllers
         /// </summary>
         public static void PasteFromClipboard()
         {
-            Surface image = GetImageFromClipboard();
-            if (image != null)
+            var images = GetImagesFromClipboard();
+
+            foreach (var (surface, name) in images)
             {
-                AddImageToLayers(image);
+                AddImageToLayers(surface, name);
                 int latestLayerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Count - 1;
                 ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(
-                    new Change(RemoveLayerProcess, new object[] { latestLayerIndex }, AddLayerProcess, new object[] { image }));
+                    new Change(RemoveLayerProcess, new object[] { latestLayerIndex }, AddLayerProcess, new object[] { images }));
             }
         }
 
@@ -58,31 +106,46 @@ namespace PixiEditor.Models.Controllers
         ///     Gets image from clipboard, supported PNG, Dib and Bitmap.
         /// </summary>
         /// <returns>WriteableBitmap.</returns>
-        public static Surface GetImageFromClipboard()
+        public static IEnumerable<(Surface, string name)> GetImagesFromClipboard()
         {
-            DataObject dao = (DataObject)Clipboard.GetDataObject();
-            WriteableBitmap finalImage = null;
-            if (dao.GetDataPresent("PNG"))
+            DataObject data = (DataObject)Clipboard.GetDataObject();
+
+            if (data.GetDataPresent("PIXI"))
             {
-                using (MemoryStream pngStream = dao.GetData("PNG") as MemoryStream)
+                SerializableDocument document = GetSerializable(data, out CropData crop);
+
+                foreach (SerializableLayer layer in document)
                 {
-                    if (pngStream != null)
+                    if (layer.OffsetX > crop.OffsetX + crop.Width || layer.OffsetY > crop.OffsetY + crop.Height ||
+                        !layer.IsVisible || layer.Opacity == 0)
                     {
-                        PngBitmapDecoder decoder = new PngBitmapDecoder(pngStream, BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.OnLoad);
-                        finalImage = new WriteableBitmap(decoder.Frames[0].Clone());
+                        continue;
                     }
+
+                    using Surface tempSurface = new Surface(layer.ToSKImage());
+
+                    yield return (tempSurface.Crop(crop.OffsetX, crop.OffsetY, crop.Width, crop.Height), layer.Name);
+                }
+            }
+            else if(data.GetDataPresent(DataFormats.FileDrop))
+            {
+                foreach (string path in data.GetFileDropList())
+                {
+                    yield return (Importer.ImportSurface(path), Path.GetFileName(path));
                 }
+
+                yield break;
             }
-            else if (dao.GetDataPresent(DataFormats.Dib))
+            else if (TryFromSingleImage(data, out Surface singleImage))
             {
-                finalImage = new WriteableBitmap(Clipboard.GetImage()!);
+                yield return (singleImage, "Copied");
+                yield break;
             }
-            else if (dao.GetDataPresent(DataFormats.Bitmap))
+            else
             {
-                finalImage = new WriteableBitmap((dao.GetData(DataFormats.Bitmap) as BitmapSource)!);
+                throw new NotImplementedException();
             }
 
-            return new Surface(finalImage);
         }
 
         public static bool IsImageInClipboard()
@@ -94,18 +157,80 @@ namespace PixiEditor.Models.Controllers
             }
 
             return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
-                   dao.GetDataPresent(DataFormats.Bitmap);
+                   dao.GetDataPresent(DataFormats.Bitmap) || dao.GetDataPresent(DataFormats.FileDrop) ||
+                   dao.GetDataPresent("PIXI");
         }
 
-        public static BitmapSource BitmapSelectionToBmpSource(WriteableBitmap bitmap, Coordinates[] selection)
+        public static BitmapSource BitmapSelectionToBmpSource(WriteableBitmap bitmap, Coordinates[] selection, out int offsetX, out int offsetY, out int width, out int height)
         {
-            int offsetX = selection.Min(x => x.X);
-            int offsetY = selection.Min(x => x.Y);
-            int width = selection.Max(x => x.X) - offsetX + 1;
-            int height = selection.Max(x => x.Y) - offsetY + 1;
+            offsetX = selection.Min(x => x.X);
+            offsetY = selection.Min(x => x.Y);
+            width = selection.Max(x => x.X) - offsetX + 1;
+            height = selection.Max(x => x.Y) - offsetY + 1;
             return bitmap.Crop(offsetX, offsetY, width, height);
         }
 
+        private static BitmapSource FromPNG(DataObject data)
+        {
+            MemoryStream pngStream = data.GetData("PNG") as MemoryStream;
+            PngBitmapDecoder decoder = new PngBitmapDecoder(pngStream, BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.OnLoad);
+
+            return decoder.Frames[0];
+        }
+
+        private static unsafe SerializableDocument GetSerializable(DataObject data, out CropData cropData)
+        {
+            MemoryStream pixiStream = data.GetData("PIXI") as MemoryStream;
+            SerializableDocument document = PixiParser.Deserialize(pixiStream);
+
+            if (data.GetDataPresent(typeof(CropData)))
+            {
+                cropData = CropData.FromStream(data.GetData(typeof(CropData)) as MemoryStream);
+            }
+            else
+            {
+                cropData = new CropData(document.Width, document.Height, 0, 0);
+            }
+
+            return document;
+        }
+
+        private static bool TryFromSingleImage(DataObject data, out Surface result)
+        {
+            BitmapSource source;
+
+            if (data.GetDataPresent("PNG"))
+            {
+                source = FromPNG(data);
+            }
+            else if (data.GetDataPresent(DataFormats.Dib) || data.GetDataPresent(DataFormats.Bitmap))
+            {
+                source = Clipboard.GetImage();
+            }
+            else
+            {
+                result = null;
+                return false;
+            }
+
+            if (source.Format == PixelFormats.Pbgra32)
+            {
+                result = new Surface(source);
+            }
+            else
+            {
+                FormatConvertedBitmap newFormat = new FormatConvertedBitmap();
+                newFormat.BeginInit();
+                newFormat.Source = source;
+                newFormat.DestinationFormat = PixelFormats.Pbgra32;
+                newFormat.EndInit();
+
+                result = new Surface(newFormat);
+            }
+
+            return true;
+        }
+
         private static void RemoveLayerProcess(object[] parameters)
         {
             if (parameters.Length == 0 || !(parameters[0] is int))
@@ -126,9 +251,9 @@ namespace PixiEditor.Models.Controllers
             AddImageToLayers((Surface)parameters[0]);
         }
 
-        private static void AddImageToLayers(Surface image)
+        private static void AddImageToLayers(Surface image, string name = "Image")
         {
-            ViewModelMain.Current.BitmapManager.ActiveDocument.AddNewLayer("Image", image);
+            ViewModelMain.Current.BitmapManager.ActiveDocument.AddNewLayer(name, image);
         }
     }
 }

+ 3 - 2
PixiEditor/Models/Controllers/LayerStackRenderer.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Models.DataHolders;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers.Utils;
 using SkiaSharp;
@@ -85,7 +86,7 @@ namespace PixiEditor.Models.Controllers
             }
             finalBitmap.Lock();
             finalSurface.SkiaSurface.Draw(backingSurface.Canvas, 0, 0, Surface.ReplacingPaint);
-            finalBitmap.AddDirtyRect(dirtyRectangle);
+            finalBitmap.AddDirtyRect(dirtyRectangle.Min(new Int32Rect(0, 0, finalBitmap.PixelWidth, finalBitmap.PixelHeight)));
             finalBitmap.Unlock();
         }
 

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

@@ -1,4 +1,5 @@
-using System;
+using PixiEditor.Parser;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;

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

@@ -46,7 +46,7 @@ namespace PixiEditor.Models.DataHolders
             SkiaSurface = Pbgra32BytesToSkSurface(w, h, pbgra32Bytes);
         }
 
-        public Surface(WriteableBitmap original)
+        public Surface(BitmapSource original)
         {
             if (original.Format != PixelFormats.Pbgra32)
                 throw new ArgumentException("This method only supports Pbgra32 bitmaps");
@@ -61,6 +61,14 @@ namespace PixiEditor.Models.DataHolders
             SkiaSurface = Pbgra32BytesToSkSurface(Width, Height, pixels);
         }
 
+        public Surface(SKImage image)
+        {
+            Width = image.Width;
+            Height = image.Height;
+            SkiaSurface = CreateSurface(Width, Height);
+            SkiaSurface.Canvas.DrawImage(image, 0, 0);
+        }
+
         public Surface ResizeNearestNeighbor(int newW, int newH)
         {
             SKImage image = SkiaSurface.Snapshot();
@@ -143,23 +151,18 @@ namespace PixiEditor.Models.DataHolders
             SkiaSurface.Dispose();
         }
 
-        private static SKSurface Pbgra32BytesToSkSurface(int w, int h, byte[] pbgra32Bytes)
+        private static unsafe SKSurface Pbgra32BytesToSkSurface(int w, int h, byte[] pbgra32Bytes)
         {
             SKImageInfo info = new SKImageInfo(w, h, SKColorType.Bgra8888, SKAlphaType.Premul);
-            var ptr = Marshal.AllocHGlobal(pbgra32Bytes.Length);
-            try
+
+            fixed (void* pointer = pbgra32Bytes)
             {
-                Marshal.Copy(pbgra32Bytes, 0, ptr, pbgra32Bytes.Length);
-                using SKPixmap map = new(info, ptr);
+                using SKPixmap map = new(info, new IntPtr(pointer));
                 using SKSurface surface = SKSurface.Create(map);
                 var newSurface = CreateSurface(w, h);
                 surface.Draw(newSurface.Canvas, 0, 0, ReplacingPaint);
                 return newSurface;
             }
-            finally
-            {
-                Marshal.FreeHGlobal(ptr);
-            }
         }
 
         private static SKSurface CreateSurface(int w, int h)

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

@@ -44,7 +44,7 @@ namespace PixiEditor.Models.IO
         /// <returns>Path.</returns>
         public static string SaveAsEditableFile(Document document, string path)
         {
-            Parser.PixiParser.Serialize(document.ToSerializable(), path);
+            Parser.PixiParser.Serialize(ParserHelpers.ToSerializable(document), path);
             return path;
         }
 

+ 3 - 2
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -3,6 +3,7 @@ using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers.Utils;
 using PixiEditor.Models.Position;
 using PixiEditor.Parser;
+using PixiEditor.Parser.Skia;
 using SkiaSharp;
 using System;
 using System.Collections.Generic;
@@ -76,11 +77,11 @@ namespace PixiEditor.Models.ImageManipulation
 
         public static WriteableBitmap GeneratePreviewBitmap(IEnumerable<SerializableLayer> layers, int width, int height, int maxPreviewWidth, int maxPreviewHeight)
         {
-            var opacityLayers = layers.Where(x => x.IsVisible && x.Opacity > 0.8f 
+            var opacityLayers = layers.Where(x => x.IsVisible && x.Opacity > 0.8f
             && x.Height > 0 && x.Width > 0);
 
             return GeneratePreviewBitmap(
-                opacityLayers.Select(x => new Surface(x.Width, x.Height, x.BitmapBytes)),
+                opacityLayers.Select(x => new Surface(x.ToSKImage())),
                 opacityLayers.Select(x => x.OffsetX),
                 opacityLayers.Select(x => x.OffsetY),
                 width,

+ 5 - 0
PixiEditor/Models/Position/Coordinates.cs

@@ -14,6 +14,11 @@ namespace PixiEditor.Models.Position
 
         public int Y { get; set; }
 
+        public static implicit operator Coordinates((int width, int height) tuple)
+        {
+            return new Coordinates(tuple.width, tuple.height);
+        }
+
         public static bool operator ==(Coordinates c1, Coordinates c2)
         {
             return c2.X == c1.X && c2.Y == c1.Y;

+ 82 - 0
PixiEditor/Models/Position/CropData.cs

@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace PixiEditor.Models.Position
+{
+    [StructLayout(LayoutKind.Explicit)]
+    public unsafe struct CropData
+    {
+        [FieldOffset(0)]
+        private readonly int _width;
+        [FieldOffset(4)]
+        private readonly int _height;
+        [FieldOffset(8)]
+        private readonly int _offsetX;
+        [FieldOffset(12)]
+        private readonly int _offsetY;
+
+        public int Width => _width;
+
+        public int Height => _height;
+
+        public int OffsetX => _offsetX;
+
+        public int OffsetY => _offsetY;
+
+        public CropData(int width, int height, int offsetX, int offsetY)
+        {
+            _width = width;
+            _height = height;
+            _offsetX = offsetX;
+            _offsetY = offsetY;
+        }
+
+        public static CropData FromByteArray(byte[] data)
+        {
+            if (data.Length != sizeof(CropData))
+            {
+                throw new ArgumentOutOfRangeException(nameof(data), $"data must be {sizeof(CropData)} long");
+            }
+
+            fixed (void* ptr = data)
+            {
+                return Marshal.PtrToStructure<CropData>(new IntPtr(ptr));
+            }
+        }
+
+        public static CropData FromStream(Stream stream)
+        {
+            if (stream.Length < sizeof(CropData))
+            {
+                throw new ArgumentOutOfRangeException(nameof(stream), $"The specified stream must be at least {sizeof(CropData)} bytes long");
+            }
+
+            byte[] buffer = new byte[sizeof(CropData)];
+            stream.Read(buffer);
+
+            return FromByteArray(buffer);
+        }
+
+        public byte[] ToByteArray()
+        {
+            IntPtr ptr = Marshal.AllocHGlobal(sizeof(CropData));
+            Marshal.StructureToPtr(this, ptr, true);
+
+            Span<byte> bytes = new Span<byte>(ptr.ToPointer(), sizeof(CropData));
+            byte[] array = bytes.ToArray();
+
+            Marshal.FreeHGlobal(ptr);
+
+            return array;
+        }
+
+        public MemoryStream ToStream()
+        {
+            MemoryStream stream = new();
+            stream.Write(ToByteArray());
+            return stream;
+        }
+
+    }
+}

+ 10 - 1
PixiEditor/PixiEditor.csproj

@@ -161,14 +161,15 @@
       <Version>1.0.2</Version>
       <NoWarn>NU1701</NoWarn>
     </PackageReference>
+    <PackageReference Include="MessagePack" Version="2.3.75" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
     <PackageReference Include="MvvmLightLibs" Version="5.4.1.1">
       <NoWarn>NU1701</NoWarn>
     </PackageReference>
     <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
     <PackageReference Include="PixiEditor.ColorPicker" Version="3.1.0" />
-    <PackageReference Include="PixiEditor.Parser" Version="1.1.3.1" />
     <PackageReference Include="SkiaSharp" Version="2.80.3" />
+    <PackageReference Include="System.Drawing.Common" Version="5.0.2" />
     <PackageReference Include="WriteableBitmapEx">
       <Version>1.6.7</Version>
     </PackageReference>
@@ -211,6 +212,14 @@
   <ItemGroup>
     <ProjectReference Include="..\PixiEditor.UpdateModule\PixiEditor.UpdateModule.csproj" />
   </ItemGroup>
+  <ItemGroup>
+    <Reference Include="PixiParser">
+      <HintPath>..\..\PixiParser\src\PixiParser.Skia\bin\Debug\net5.0\PixiParser.dll</HintPath>
+    </Reference>
+    <Reference Include="PixiParser.Skia">
+      <HintPath>..\..\PixiParser\src\PixiParser.Skia\bin\Debug\net5.0\PixiParser.Skia.dll</HintPath>
+    </Reference>
+  </ItemGroup>
   <ItemGroup>
     <Compile Update="Properties\Settings.Designer.cs">
       <DesignTimeSharedInput>True</DesignTimeSharedInput>

+ 2 - 7
PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs

@@ -23,7 +23,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public ClipboardViewModel(ViewModelMain owner)
             : base(owner)
         {
-            CopyCommand = new RelayCommand(Copy, Owner.SelectionSubViewModel.SelectionIsNotEmpty);
+            CopyCommand = new RelayCommand(Copy);
             DuplicateCommand = new RelayCommand(Duplicate, Owner.SelectionSubViewModel.SelectionIsNotEmpty);
             CutCommand = new RelayCommand(Cut, Owner.SelectionSubViewModel.SelectionIsNotEmpty);
             PasteCommand = new RelayCommand(Paste, CanPaste);
@@ -55,12 +55,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private void Copy(object parameter)
         {
-            var doc = Owner.BitmapManager.ActiveDocument;
-            ClipboardController.CopyToClipboard(
-                doc.Layers.Where(x => x.IsActive && doc.GetFinalLayerIsVisible(x)).ToArray(),
-                doc.ActiveSelection.SelectedPoints.ToArray(),
-                doc.Width,
-                doc.Height);
+            ClipboardController.CopyToClipboard(Owner.BitmapManager.ActiveDocument);
         }
     }
 }

+ 1 - 0
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -261,6 +261,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// <param name="parameter">CommandProperty.</param>
         private void ExportFile(object parameter)
         {
+            ViewModelMain.Current.ActionDisplay = "";
             WriteableBitmap bitmap = Owner.BitmapManager.ActiveDocument.Renderer.FinalBitmap;
             Exporter.Export(bitmap, new Size(bitmap.PixelWidth, bitmap.PixelHeight));
         }

+ 4 - 4
PixiEditorTests/ModelsTests/ControllersTests/ClipboardControllerTests.cs

@@ -22,7 +22,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         {
             Clipboard.Clear();
             Clipboard.SetText(Text);
-            Surface img = ClipboardController.GetImageFromClipboard();
+            Surface img = ClipboardController.GetImagesFromClipboard();
             Assert.Null(img);
         }
 
@@ -78,7 +78,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             bmp.SetSRGBPixel(4, 4, testColor);
             Clipboard.SetImage(bmp);
 
-            Surface img = ClipboardController.GetImageFromClipboard();
+            Surface img = ClipboardController.GetImagesFromClipboard();
             Assert.NotNull(img);
             Assert.Equal(10, img.Width);
             Assert.Equal(10, img.Height);
@@ -102,7 +102,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
                 Clipboard.SetDataObject(data, true);
             }
 
-            Surface img = ClipboardController.GetImageFromClipboard();
+            Surface img = ClipboardController.GetImagesFromClipboard();
             Assert.NotNull(img);
             Assert.Equal(10, img.Width);
             Assert.Equal(10, img.Height);
@@ -120,7 +120,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             data.SetData(DataFormats.Bitmap, bmp, false); // PNG, supports transparency
             Clipboard.SetDataObject(data, true);
 
-            Surface img = ClipboardController.GetImageFromClipboard();
+            Surface img = ClipboardController.GetImagesFromClipboard();
             Assert.NotNull(img);
             Assert.Equal(10, img.Width);
             Assert.Equal(10, img.Height);