Browse Source

Fixed clipboard and implemented Undo Change Disposing

CPKreuz 3 years ago
parent
commit
ce436dc04b

+ 11 - 0
PixiEditor/Helpers/Extensions/PixiParserHelper.cs

@@ -0,0 +1,11 @@
+using PixiEditor.Parser;
+using SkiaSharp;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class PixiParserHelper
+    {
+        public static SKRectI GetRect(this SerializableLayer layer) => 
+            SKRectI.Create(layer.OffsetX, layer.OffsetY, layer.Width, layer.Height);
+    }
+}

+ 1 - 1
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -124,7 +124,7 @@ namespace PixiEditor.Models.Controllers
 
             Documents.Remove(document);
             ActiveDocument = nextIndex >= 0 ? Documents[nextIndex] : null;
-            document.DisposeLayerBitmaps();
+            document.Dispose();
         }
 
         public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)

+ 116 - 63
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Helpers.Extensions;
+using PixiEditor.Exceptions;
+using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.IO;
@@ -6,7 +7,6 @@ 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;
@@ -78,8 +78,8 @@ namespace PixiEditor.Models.Controllers
         {
             CopyToClipboard(
                 document.Layers.Where(x => document.GetFinalLayerIsVisible(x)).ToArray(),
-                //doc.ActiveSelection.SelectedPoints.ToArray(),
-                new Coordinates[] { (0, 0), (15, 15) },
+                document.ActiveSelection.SelectedPoints.ToArray(),
+                //new Coordinates[] { (0, 0), (15, 15) },
                 document.Width,
                 document.Height,
                 document.ToSerializable());
@@ -90,62 +90,81 @@ namespace PixiEditor.Models.Controllers
         /// </summary>
         public static void PasteFromClipboard()
         {
-            var images = GetImagesFromClipboard();
+            var layers = GetLayersFromClipboard();
 
-            foreach (var (surface, name) in images)
+            Document activeDocument = ViewModelMain.Current.BitmapManager.ActiveDocument;
+            int startIndex = activeDocument.Layers.Count;
+
+            foreach (var layer in layers)
             {
-                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[] { images }));
+                activeDocument.Layers.Add(layer);
             }
-        }
 
+            activeDocument.UndoManager.AddUndoChange(
+                new Change(RemoveLayersProcess, new object[] { startIndex }, AddLayersProcess, new object[] { layers }) { DisposeProcess = DisposeProcess });
+        }
 
         /// <summary>
         ///     Gets image from clipboard, supported PNG, Dib and Bitmap.
         /// </summary>
         /// <returns>WriteableBitmap.</returns>
-        public static IEnumerable<(Surface, string name)> GetImagesFromClipboard()
+        public static IEnumerable<Layer> GetLayersFromClipboard()
         {
             DataObject data = (DataObject)Clipboard.GetDataObject();
 
             if (data.GetDataPresent("PIXI"))
             {
                 SerializableDocument document = GetSerializable(data, out CropData crop);
+                SKRectI cropRect = SKRectI.Create(crop.OffsetX, crop.OffsetY, crop.Width, crop.Height);
 
-                foreach (SerializableLayer layer in document)
+                foreach (SerializableLayer sLayer in document)
                 {
-                    if (layer.OffsetX > crop.OffsetX + crop.Width || layer.OffsetY > crop.OffsetY + crop.Height ||
-                        !layer.IsVisible || layer.Opacity == 0)
+                    SKRectI intersect;
+
+                    if (/*layer.OffsetX > crop.OffsetX + crop.Width || layer.OffsetY > crop.OffsetY + crop.Height ||*/
+                        !sLayer.IsVisible || sLayer.Opacity == 0 ||
+                        (intersect = SKRectI.Intersect(cropRect, sLayer.GetRect())) == SKRectI.Empty)
                     {
                         continue;
                     }
 
-                    using Surface tempSurface = new Surface(layer.ToSKImage());
+                    var layer = sLayer.ToLayer();
 
-                    yield return (tempSurface.Crop(crop.OffsetX, crop.OffsetY, crop.Width, crop.Height), layer.Name);
+                    layer.Crop(intersect);
+
+                    yield return layer;
                 }
             }
             else if (TryFromSingleImage(data, out Surface singleImage))
             {
-                yield return (singleImage, "Copied");
-                yield break;
+                yield return new Layer("Image", singleImage);
             }
-            else if(data.GetDataPresent(DataFormats.FileDrop))
+            else if (data.GetDataPresent(DataFormats.FileDrop))
             {
                 foreach (string path in data.GetFileDropList())
                 {
-                    yield return (Importer.ImportSurface(path), Path.GetFileName(path));
-                }
+                    if (!Importer.IsSupportedFile(path))
+                    {
+                        continue;
+                    }
+
+                    Layer layer = null;
+
+                    try
+                    {
+                        layer = new(Path.GetFileName(path), Importer.ImportSurface(path));
+                    }
+                    catch (CorruptedFileException)
+                    {
+                    }
 
-                yield break;
+                    yield return layer ?? new($"Corrupt {path}");
+                }
             }
             else
             {
                 throw new NotImplementedException();
             }
-
         }
 
         public static bool IsImageInClipboard()
@@ -156,6 +175,19 @@ namespace PixiEditor.Models.Controllers
                 return false;
             }
 
+            var files = dao.GetFileDropList();
+
+            if (files != null)
+            {
+                foreach (var file in files)
+                {
+                    if (Importer.IsSupportedFile(file))
+                    {
+                        return true;
+                    }
+                }
+            }
+
             return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
                    dao.GetDataPresent(DataFormats.Bitmap) || dao.GetDataPresent(DataFormats.FileDrop) ||
                    dao.GetDataPresent("PIXI");
@@ -163,10 +195,10 @@ namespace PixiEditor.Models.Controllers
 
         public static BitmapSource BitmapSelectionToBmpSource(WriteableBitmap bitmap, Coordinates[] selection, out int offsetX, out int offsetY, out int width, out int height)
         {
-            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;
+            offsetX = selection.Min(min => min.X);
+            offsetY = selection.Min(min => min.Y);
+            width = selection.Max(max => max.X) - offsetX + 1;
+            height = selection.Max(max => max.Y) - offsetY + 1;
             return bitmap.Crop(offsetX, offsetY, width, height);
         }
 
@@ -197,63 +229,84 @@ namespace PixiEditor.Models.Controllers
 
         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
+            try
             {
-                result = null;
-                return false;
-            }
+                BitmapSource source;
 
-            if (source.Format.IsSkiaSupported())
-            {
-                result = new Surface(source);
-            }
-            else
-            {
-                FormatConvertedBitmap newFormat = new FormatConvertedBitmap();
-                newFormat.BeginInit();
-                newFormat.Source = source;
-                newFormat.DestinationFormat = PixelFormats.Rgba64;
-                newFormat.EndInit();
+                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;
+                }
 
-                result = new Surface(newFormat);
+                if (source.Format.IsSkiaSupported())
+                {
+                    result = new Surface(source);
+                }
+                else
+                {
+                    FormatConvertedBitmap newFormat = new FormatConvertedBitmap();
+                    newFormat.BeginInit();
+                    newFormat.Source = source;
+                    newFormat.DestinationFormat = PixelFormats.Rgba64;
+                    newFormat.EndInit();
+
+                    result = new Surface(newFormat);
+                }
+
+                return true;
             }
+            catch { }
 
-            return true;
+            result = null;
+            return false;
         }
 
-        private static void RemoveLayerProcess(object[] parameters)
+        private static void RemoveLayersProcess(object[] parameters)
         {
-            if (parameters.Length == 0 || !(parameters[0] is int))
+            if (parameters.Length == 0 || parameters[0] is not int i)
             {
                 return;
             }
 
-            ViewModelMain.Current.BitmapManager.ActiveDocument.RemoveLayer((int)parameters[0], true);
+            Document document = ViewModelMain.Current.BitmapManager.ActiveDocument;
+
+            while (i < document.Layers.Count)
+            {
+                document.RemoveLayer(i, true);
+            }
         }
 
-        private static void AddLayerProcess(object[] parameters)
+        private static void AddLayersProcess(object[] parameters)
         {
-            if (parameters.Length == 0 || !(parameters[0] is Surface))
+            if (parameters.Length == 0 || parameters[0] is not IEnumerable<Layer> layers)
             {
                 return;
             }
 
-            AddImageToLayers((Surface)parameters[0]);
+            foreach (var layer in layers)
+            {
+                ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Add(layer);
+            }
         }
 
-        private static void AddImageToLayers(Surface image, string name = "Image")
+        private static void DisposeProcess(object[] rev, object[] proc)
         {
-            ViewModelMain.Current.BitmapManager.ActiveDocument.AddNewLayer(name, image);
+            if (proc[0] is IEnumerable<Layer> layers)
+            {
+                foreach (var layer in layers)
+                {
+                    layer.LayerBitmap.Dispose();
+                }
+            }
         }
     }
 }

+ 11 - 1
PixiEditor/Models/Controllers/UndoManager.cs

@@ -9,7 +9,7 @@ using PixiEditor.ViewModels;
 namespace PixiEditor.Models.Controllers
 {
     [DebuggerDisplay("{UndoStack.Count} undo steps, {RedoStack.Count} redo step(s)")]
-    public class UndoManager
+    public class UndoManager : IDisposable
     {
         private bool lastChangeWasUndo;
 
@@ -158,6 +158,16 @@ namespace PixiEditor.Models.Controllers
             AddUndoChange(change);
         }
 
+        public void Dispose()
+        {
+            foreach (Change change in UndoStack.Concat(RedoStack))
+            {
+                change.Dispose();
+            }
+
+            GC.SuppressFinalize(this);
+        }
+
         private bool ChangeIsBlockedProperty(Change change)
         {
             return (change.Root != null || change.FindRootProcess != null)

+ 7 - 6
PixiEditor/Models/DataHolders/Document/Document.Layers.cs

@@ -494,12 +494,18 @@ namespace PixiEditor.Models.DataHolders
             return layer;
         }
 
-        public void DisposeLayerBitmaps()
+        public SKColor GetColorAtPoint(int x, int y)
+        {
+            return Renderer.FinalSurface.GetSRGBPixel(x, y);
+        }
+
+        private void DisposeLayerBitmaps()
         {
             foreach (var layer in layers)
             {
                 layer.LayerBitmap.Dispose();
             }
+
             referenceLayer?.LayerBitmap.Dispose();
             previewLayer?.LayerBitmap.Dispose();
 
@@ -508,11 +514,6 @@ namespace PixiEditor.Models.DataHolders
             renderer?.Dispose();
         }
 
-        public SKColor GetColorAtPoint(int x, int y)
-        {
-            return Renderer.FinalSurface.GetSRGBPixel(x, y);
-        }
-
         private void BuildLayerStructureProcess(object[] parameters)
         {
             if (parameters.Length > 0 && parameters[0] is ObservableCollection<GuidStructureItem> groups)

+ 9 - 1
PixiEditor/Models/DataHolders/Document/Document.cs

@@ -19,7 +19,7 @@ using System.Windows;
 namespace PixiEditor.Models.DataHolders
 {
     [DebuggerDisplay("'{Name, nq}' {width}x{height} {Layers.Count} Layer(s)")]
-    public partial class Document : NotifyableObject
+    public partial class Document : NotifyableObject, IDisposable
     {
 
         private ViewModelMain xamlAccesibleViewModel = null;
@@ -185,6 +185,14 @@ namespace PixiEditor.Models.DataHolders
                     "Center content"));
         }
 
+        public void Dispose()
+        {
+            DisposeLayerBitmaps();
+            UndoManager.Dispose();
+
+            GC.SuppressFinalize(this);
+        }
+
         private void SetAsActiveOnClick(object obj)
         {
             XamlAccesibleViewModel.BitmapManager.MouseController.StopRecordingMouseMovementChanges();

+ 80 - 0
PixiEditor/Models/DataHolders/Surface.cs

@@ -2,7 +2,9 @@
 using PixiEditor.Models.Position;
 using SkiaSharp;
 using System;
+using System.Collections;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Windows;
@@ -195,5 +197,83 @@ namespace PixiEditor.Models.DataHolders
                 surface.Draw(SkiaSurface.Canvas, 0, 0, ReplacingPaint);
             }
         }
+
+#if DEBUG
+        // Used to iterate the surface's pixels during development
+
+        [Obsolete("Only meant for use in a debugger like Visual Studio", true)]
+        private SurfaceDebugger Debugger => new SurfaceDebugger(this);
+
+        [Obsolete("Only meant for use in a debugger like Visual Studio", true)]
+        private class SurfaceDebugger : IEnumerable
+        {
+            private readonly Surface _surface;
+
+            public SurfaceDebugger(Surface surface)
+            {
+                _surface = surface;
+            }
+
+            IEnumerator IEnumerable.GetEnumerator()
+            {
+                var pixmap = _surface.SkiaSurface.PeekPixels();
+
+                for (int y = 0; y < pixmap.Width; y++)
+                {
+                    yield return new DebugPixel(y);
+
+                    for (int x = 0; x < pixmap.Height; x++)
+                    {
+                        yield return new DebugPixel(x, y, pixmap.GetPixelColor(x, y).ToString());
+                    }
+                }
+            }
+
+            [DebuggerDisplay("{DebuggerDisplay,nq}")]
+            private struct DebugPixel
+            {
+                [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+                private string DebuggerDisplay
+                {
+                    get
+                    {
+                        if (isPixel)
+                        {
+                            return $"X: {x}; Y: {y} - {hex}";
+                        }
+                        else
+                        {
+                            return $"|- Y: {y} -|";
+                        }
+                    }
+                }
+
+                [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+                private readonly int x;
+                [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+                private readonly int y;
+                [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+                private readonly string hex;
+                [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+                private readonly bool isPixel;
+
+                public DebugPixel(int y)
+                {
+                    x = 0;
+                    this.y = y;
+                    hex = null;
+                    isPixel = false;
+                }
+
+                public DebugPixel(int x, int y, string hex)
+                {
+                    this.x = x;
+                    this.y = y;
+                    this.hex = hex;
+                    isPixel = true;
+                }
+            }
+        }
+#endif
     }
 }

+ 30 - 1
PixiEditor/Models/Layers/Layer.cs

@@ -405,7 +405,7 @@ namespace PixiEditor.Models.Layers
         public void CreateNewBitmap(int width, int height)
         {
             LayerBitmap = new Surface(width, height);
-            
+
             Width = width;
             Height = height;
         }
@@ -528,6 +528,35 @@ namespace PixiEditor.Models.Layers
             return LayerBitmap.ToByteArray();
         }
 
+        public SKRectI GetRect() => SKRectI.Create(OffsetX, OffsetY, Width, Height);
+
+        public void CropIntersect(SKRectI rect)
+        {
+            SKRectI layerRect = GetRect();
+            SKRectI intersect = SKRectI.Intersect(layerRect, rect);
+
+            Crop(intersect);
+        }
+
+        public void Crop(SKRectI intersect)
+        {
+            if (intersect == SKRectI.Empty)
+            {
+                return;
+            }
+
+            using var oldSurface = LayerBitmap;
+
+            int offsetX = (int)(Offset.Left - intersect.Left);
+            int offsetY = (int)(Offset.Top - intersect.Top);
+
+            Width = intersect.Width;
+            Height = intersect.Height;
+            LayerBitmap = LayerBitmap.Crop(offsetX, offsetY, Width, Height);
+
+            Offset = new(intersect.Left, intersect.Top, 0, 0);
+        }
+
         private Dictionary<Coordinates, SKColor> GetRelativePosition(Dictionary<Coordinates, SKColor> changedPixels)
         {
             return changedPixels.ToDictionary(

+ 13 - 1
PixiEditor/Models/Undo/Change.cs

@@ -1,9 +1,10 @@
 using System;
+using System.Linq;
 
 namespace PixiEditor.Models.Undo
 {
     [Serializable]
-    public class Change
+    public class Change : IDisposable
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="Change"/> class.
@@ -123,5 +124,16 @@ namespace PixiEditor.Models.Undo
         public Func<object[], object> FindRootProcess { get; set; }
 
         public object[] FindRootProcessArgs { get; set; }
+
+        public Action<object[], object[]> DisposeProcess { get; set; }
+
+        public void Dispose()
+        {
+            DisposeProcess?.Invoke(
+                ReverseProcessArguments ?? Array.Empty<object>(),
+                ProcessArguments ?? Array.Empty<object>());
+
+            GC.SuppressFinalize(this);
+        }
     }
 }