Browse Source

Saving docs works

flabbet 2 years ago
parent
commit
1ad7f6d241

+ 45 - 0
src/PixiEditor.ChangeableDocument/Changeables/Document.cs

@@ -35,6 +35,51 @@ internal class Document : IChangeable, IReadOnlyDocument, IDisposable
         Selection.Dispose();
         Selection.Dispose();
     }
     }
     
     
+    /// <summary>
+    ///     Creates a surface for layer image.
+    /// </summary>
+    /// <param name="layerGuid">Guid of the layer inside structure.</param>
+    /// <returns>Surface if the layer has some drawn pixels, null if the image is empty.</returns>
+    /// <exception cref="ArgumentException">Exception when guid is not found inside structure or if it's not a layer</exception>
+    /// <remarks>So yeah, welcome folks to the multithreaded world, where possibilities are endless! (and chances of objects getting
+    /// edited, in between of processing you want to make exist). You might encounter ObjectDisposedException and other mighty creatures here if
+    /// you are lucky enough. Have fun!</remarks>
+    public Surface? GetLayerImage(Guid layerGuid)
+    {
+        var layer = (IReadOnlyLayer?)FindMember(layerGuid);
+
+        if (layer is null)
+            throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));
+
+
+        RectI? tightBounds = layer.LayerImage.FindLatestBounds();
+
+        if (tightBounds is null)
+            return null;
+
+        tightBounds = tightBounds.Value.Intersect(RectI.Create(0, 0, Size.X, Size.Y));
+
+        Surface surface = new Surface(tightBounds.Value.Size);
+
+        layer.LayerImage.DrawMostUpToDateRegionOn(
+            tightBounds.Value,
+            ChunkResolution.Full,
+            surface.DrawingSurface, VecI.Zero);
+
+        return surface;
+    }
+
+    public RectI? GetLayerTightBounds(Guid layerGuid)
+    {
+        var layer = (IReadOnlyLayer?)FindMember(layerGuid);
+
+        if (layer is null)
+            throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));
+
+
+        return layer.LayerImage.FindLatestBounds();
+    }
+    
     public void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action) => ForEveryReadonlyMember(StructureRoot, action);
     public void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action) => ForEveryReadonlyMember(StructureRoot, action);
 
 
     /// <summary>
     /// <summary>

+ 3 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -43,6 +43,9 @@ public interface IReadOnlyDocument
     /// Performs the specified action on each readonly member of the document
     /// Performs the specified action on each readonly member of the document
     /// </summary>
     /// </summary>
     void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action);
     void ForEveryReadonlyMember(Action<IReadOnlyStructureMember> action);
+    
+    public Surface? GetLayerImage(Guid layerGuid);
+    public RectI? GetLayerTightBounds(Guid layerGuid);
 
 
     /// <summary>
     /// <summary>
     /// Finds the member with the <paramref name="guid"/> or returns null if not found
     /// Finds the member with the <paramref name="guid"/> or returns null if not found

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

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

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

@@ -30,4 +30,9 @@ public class ImgData : NativeObject
     {
     {
         return DrawingBackendApi.Current.ImgDataImplementation.AsStream(this);
         return DrawingBackendApi.Current.ImgDataImplementation.AsStream(this);
     }
     }
+
+    public ReadOnlySpan<byte> AsSpan()
+    {
+        return DrawingBackendApi.Current.ImgDataImplementation.AsSpan(this);
+    }
 }
 }

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

@@ -29,5 +29,11 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             SKData data = ManagedInstances[imgData.ObjectPointer];
             SKData data = ManagedInstances[imgData.ObjectPointer];
             return data.AsStream();
             return data.AsStream();
         }
         }
+
+        public ReadOnlySpan<byte> AsSpan(ImgData imgData)
+        {
+            SKData data = ManagedInstances[imgData.ObjectPointer];
+            return data.AsSpan();
+        }
     }
     }
 }
 }

+ 14 - 82
src/PixiEditor/Helpers/Extensions/ParserHelpers.cs

@@ -8,6 +8,7 @@ using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.Parser;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Collections;
 using PixiEditor.Parser.Collections;
+using PixiEditor.Parser.Skia;
 using PixiEditor.ViewModels.SubViewModels.Document;
 using PixiEditor.ViewModels.SubViewModels.Document;
 
 
 namespace PixiEditor.Helpers.Extensions;
 namespace PixiEditor.Helpers.Extensions;
@@ -100,7 +101,17 @@ internal static class ParserHelpers
                         .WithOpacity(layer.Opacity)
                         .WithOpacity(layer.Opacity)
                         .WithVisibility(layer.IsVisible)
                         .WithVisibility(layer.IsVisible)
                         .WithRect(layer.Width, layer.Height, layer.OffsetX, layer.OffsetY)
                         .WithRect(layer.Width, layer.Height, layer.OffsetX, layer.OffsetY)
-                        .WithSurface((surfaceBuilder) => surfaceBuilder.WithImage(layer.PngBytes))
+                        .WithSurface((surfaceBuilder) =>
+                        {
+                            if (layer.PngBytes is { Length: > 0 })
+                            {
+                                surfaceBuilder.WithImage(layer.PngBytes);
+                            }
+                            else
+                            {
+                                surfaceBuilder.Surface = new Surface(new VecI(1, 1));
+                            }
+                        })
                         .WithOrderInStructure(document.Layers.IndexOf(layer));
                         .WithOrderInStructure(document.Layers.IndexOf(layer));
                 });
                 });
             }
             }
@@ -162,93 +173,14 @@ internal static class ParserHelpers
             previousOrder = order;
             previousOrder = order;
         }
         }
     }
     }
-    
-    public static SerializableDocument ToSerializable(this DocumentViewModel documentViewModel)
-    {
-        return new SerializableDocument(documentViewModel.Width, documentViewModel.Height,
-                ToSerializableGroups(documentViewModel.StructureRoot, documentViewModel),
-                ToSerializableLayers(documentViewModel.StructureRoot, documentViewModel))
-            .AddSwatches(documentViewModel.Swatches)
-            .AddPalette(documentViewModel.Palette);
-    }
-
-    private static List<SerializableLayer> ToSerializableLayers(FolderViewModel documentStructureRoot, DocumentViewModel document)
-    {
-        List<SerializableLayer> layers = new List<SerializableLayer>();
-        
-        Traverse(documentStructureRoot, member =>
-        {
-            if (member is LayerViewModel layer)
-            {
-                layers.Add(layer.ToSerializable(document));
-            }
-        });
-        
-        return layers;
-    }
-
-    private static SerializableLayer ToSerializable(this LayerViewModel layer, DocumentViewModel document)
-    {
-        var result = document.GetLayerImage(layer.GuidValue);
-
-        return new SerializableLayer();
-    }
-
-    private static List<SerializableGroup> ToSerializableGroups(FolderViewModel documentStructureRoot, DocumentViewModel documentViewModel, int passIndex = 0)
-    {
-        List<SerializableGroup> group = new List<SerializableGroup>();
-        
-        int currentLayerIndex = passIndex;
-        foreach (var memberViewModel in documentStructureRoot.Children)
-        {
-            if (memberViewModel is FolderViewModel folder && folder != documentViewModel.StructureRoot)
-            {
-                int startIndex = currentLayerIndex;
-                int endIndex = GetEndIndex(folder, startIndex);
-                group.Add(new SerializableGroup(folder.NameBindable, startIndex, endIndex, ToSerializableGroups(folder, documentViewModel, passIndex)));        
-            }
-            else if(memberViewModel is LayerViewModel layer)
-            {
-                currentLayerIndex++;
-            }
-        }
-        
-        return group;
-    }
-
-    private static int GetEndIndex(FolderViewModel folder, int startIndex)
-    {
-        int endIndex = startIndex;
-        Traverse(folder, member =>
-        {
-            if (member is LayerViewModel)
-            {
-                endIndex++;
-            }
-        });
-        
-        return endIndex;
-    }
-
-    private static void Traverse(FolderViewModel root, Action<StructureMemberViewModel> action)
-    {
-        foreach (var child in root.Children)
-        {
-            action(child);
-            if (child is FolderViewModel folder)
-            {
-                Traverse(folder, action);
-            }
-        }
-    }
 
 
-    private static SerializableDocument AddSwatches(this SerializableDocument document, IEnumerable<Color> colors)
+    public static SerializableDocument AddSwatches(this SerializableDocument document, IEnumerable<Color> colors)
     {
     {
         document.Swatches.AddRange(colors.Select(x => System.Drawing.Color.FromArgb(x.A, x.R, x.G, x.B)));
         document.Swatches.AddRange(colors.Select(x => System.Drawing.Color.FromArgb(x.A, x.R, x.G, x.B)));
         return document;
         return document;
     }
     }
 
 
-    private static SerializableDocument AddPalette(this SerializableDocument document, IEnumerable<Color> palette)
+    public static SerializableDocument AddPalette(this SerializableDocument document, IEnumerable<Color> palette)
     {
     {
         document.Palette.AddRange(palette.Select(x => System.Drawing.Color.FromArgb(x.A, x.R, x.G, x.B)));
         document.Palette.AddRange(palette.Select(x => System.Drawing.Color.FromArgb(x.A, x.R, x.G, x.B)));
         return document;
         return document;

+ 112 - 0
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.Serialization.cs

@@ -0,0 +1,112 @@
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Parser;
+using TerraFX.Interop.Windows;
+
+namespace PixiEditor.ViewModels.SubViewModels.Document;
+
+internal partial class DocumentViewModel
+{
+    public SerializableDocument ToSerializable()
+    {
+        IReadOnlyDocument doc = Internals.Tracker.Document;
+
+        SerializableDocument document = new SerializableDocument(Width, Height,
+            ToSerializableGroups(doc.StructureRoot, doc),
+            ToSerializableLayers(doc))
+            .AddSwatches(Swatches)
+            .AddPalette(Palette);
+
+        return document;
+    }
+
+    private static List<SerializableLayer> ToSerializableLayers(IReadOnlyDocument document)
+    {
+        List<SerializableLayer> layers = new List<SerializableLayer>();
+        
+        document.ForEveryReadonlyMember(member =>
+        {
+            if (member is IReadOnlyLayer layer)
+            {
+                SerializableLayer serializable = ToSerializable(layer, document);
+                if (serializable != null)
+                {
+                    layers.Add(serializable);
+                }
+            }
+        });
+        
+        return layers;
+    }
+
+    private static SerializableLayer ToSerializable(IReadOnlyLayer layer, IReadOnlyDocument document)
+    {
+        var result = document.GetLayerImage(layer.GuidValue);
+
+        if (result != null)
+        {
+            RectI tightBounds = document.GetLayerTightBounds(layer.GuidValue).Value;
+            var serializable = new SerializableLayer(result.Size.X, result.Size.Y, tightBounds.X, tightBounds.Y)
+                { IsVisible = layer.IsVisible, Name = layer.Name, Opacity = layer.Opacity };
+            using var data = result.DrawingSurface.Snapshot().Encode();
+            byte[] bytes = data.AsSpan().ToArray();
+            serializable.PngBytes = bytes;
+            
+            return serializable;
+        }
+
+        return new SerializableLayer(1, 1) { Name = layer.Name, IsVisible = layer.IsVisible, Opacity = layer.Opacity };
+    }
+
+    private static List<SerializableGroup> ToSerializableGroups(IReadOnlyFolder documentStructureRoot, IReadOnlyDocument document, int passIndex = 0)
+    {
+        List<SerializableGroup> group = new List<SerializableGroup>();
+        
+        int currentLayerIndex = passIndex;
+        foreach (var memberViewModel in documentStructureRoot.Children)
+        {
+            if (memberViewModel is IReadOnlyFolder folder && folder != document.StructureRoot)
+            {
+                int startIndex = currentLayerIndex;
+                int endIndex = GetEndIndex(folder, startIndex);
+                group.Add(new SerializableGroup(folder.Name, startIndex, endIndex, ToSerializableGroups(folder, document, startIndex)));        
+            }
+            else if(memberViewModel is IReadOnlyLayer)
+            {
+                currentLayerIndex++;
+            }
+        }
+        
+        return group;
+    }
+
+    private static int GetEndIndex(IReadOnlyFolder folder, int startIndex)
+    {
+        int endIndex = startIndex - 1;
+        Traverse(folder, member =>
+        {
+            if (member is IReadOnlyLayer)
+            {
+                endIndex++;
+            }
+        });
+        
+        return endIndex;
+    }
+    
+    private static void Traverse(IReadOnlyFolder folder, Action<IReadOnlyStructureMember> action)
+    {
+        action(folder);
+        foreach (var member in folder.Children)
+        {
+            if (member is IReadOnlyLayer)
+            {
+                action(member);
+            }
+            if (member is IReadOnlyFolder subFolder)
+            {
+                Traverse(subFolder, action);
+            }
+        }
+    }
+}

+ 1 - 36
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -24,7 +24,7 @@ using Colors = PixiEditor.DrawingApi.Core.ColorsImpl.Colors;
 namespace PixiEditor.ViewModels.SubViewModels.Document;
 namespace PixiEditor.ViewModels.SubViewModels.Document;
 
 
 #nullable enable
 #nullable enable
-internal class DocumentViewModel : NotifyableObject
+internal partial class DocumentViewModel : NotifyableObject
 {
 {
     public event EventHandler<LayersChangedEventArgs>? LayersChanged;
     public event EventHandler<LayersChangedEventArgs>? LayersChanged;
     public event EventHandler<DocumentSizeChangedEventArgs>? SizeChanged;
     public event EventHandler<DocumentSizeChangedEventArgs>? SizeChanged;
@@ -180,41 +180,6 @@ internal class DocumentViewModel : NotifyableObject
         PreviewBitmap = new WriteableBitmap(previewSize.X, previewSize.Y, 96, 96, PixelFormats.Pbgra32, null);
         PreviewBitmap = new WriteableBitmap(previewSize.X, previewSize.Y, 96, 96, PixelFormats.Pbgra32, null);
         PreviewSurface = DrawingSurface.Create(new ImageInfo(previewSize.X, previewSize.Y, ColorType.Bgra8888), PreviewBitmap.BackBuffer, PreviewBitmap.BackBufferStride);
         PreviewSurface = DrawingSurface.Create(new ImageInfo(previewSize.X, previewSize.Y, ColorType.Bgra8888), PreviewBitmap.BackBuffer, PreviewBitmap.BackBufferStride);
     }
     }
-    
-    /// <summary>
-    ///     Creates a surface for layer image.
-    /// </summary>
-    /// <param name="layerGuid">Guid of the layer inside structure.</param>
-    /// <returns>Surface if the layer has some drawn pixels, null if the image is empty.</returns>
-    /// <exception cref="ArgumentException">Exception when guid is not found inside structure or if it's not a layer</exception>
-    /// <remarks>So yeah, welcome folks to the multithreaded world, where possibilities are endless! (and chances of objects getting
-    /// edited, in between of processing you want to make exist). You might encounter ObjectDisposedException and other mighty creatures here if
-    /// you are lucky enough. Have fun!</remarks>
-    public Surface? GetLayerImage(Guid layerGuid)
-    {
-        IReadOnlyDocument document = Internals.Tracker.Document;
-        var layer = (IReadOnlyLayer?)document.FindMember(layerGuid);
-
-        if (layer is null)
-            throw new ArgumentException(@"The given guid does not belong to a layer.", nameof(layerGuid));
-
-
-        RectI? tightBounds = layer.LayerImage.FindLatestBounds();
-
-        if (tightBounds is null)
-            return null;
-
-        tightBounds = tightBounds.Value.Intersect(RectI.Create(0, 0, document.Size.X, document.Size.Y));
-
-        Surface surface = new Surface(tightBounds.Value.Size);
-
-        layer.LayerImage.DrawMostUpToDateRegionOn(
-            tightBounds.Value,
-            ChunkResolution.Full,
-            surface.DrawingSurface, VecI.Zero);
-
-        return surface;
-    }
 
 
     public static DocumentViewModel Build(Action<DocumentViewModelBuilder> builder)
     public static DocumentViewModel Build(Action<DocumentViewModelBuilder> builder)
     {
     {