Browse Source

Serialization and deserialization wip (bumped skia also)

flabbet 1 year ago
parent
commit
b8d47613d1

+ 3 - 1
src/PixiEditor.AvaloniaUI/Data/Localization/Languages/en.json

@@ -604,5 +604,7 @@
   "BMP_FILE": "BMP Images",
   "IMAGE_FILES": "Image Files",
   "VIDEO_FILES": "Video Files",
-  "MP4_FILE": "MP4 Videos"
+  "MP4_FILE": "MP4 Videos",
+  "COLUMNS": "Columns",
+    "ROWS": "Rows"
 }

+ 184 - 59
src/PixiEditor.AvaloniaUI/Helpers/DocumentViewModelBuilder.cs

@@ -5,11 +5,13 @@ using System.Runtime.InteropServices;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
+using PixiEditor.AvaloniaUI.Views.Animations;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.Extensions.CommonApi.Palettes;
 using PixiEditor.Numerics;
 using PixiEditor.Parser;
+using PixiEditor.Parser.Helpers;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 namespace PixiEditor.AvaloniaUI.Helpers;
@@ -18,11 +20,12 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
 {
     public int Width { get; set; }
     public int Height { get; set; }
-    
+
     public List<PaletteColor> Swatches { get; set; } = new List<PaletteColor>();
     public List<PaletteColor> Palette { get; set; } = new List<PaletteColor>();
-    
+
     public ReferenceLayerBuilder ReferenceLayer { get; set; }
+    public List<KeyFrameBuilder> AnimationData { get; set; } = new List<KeyFrameBuilder>();
 
     public DocumentViewModelBuilder WithSize(int width, int height)
     {
@@ -33,16 +36,16 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
     }
 
     public DocumentViewModelBuilder WithSize(VecI size) => WithSize(size.X, size.Y);
-    
+
     public DocumentViewModelBuilder WithSwatches(IEnumerable<PaletteColor> swatches)
     {
-        Swatches = new (swatches);
+        Swatches = new(swatches);
         return this;
     }
 
     public DocumentViewModelBuilder WithSwatches<T>(IEnumerable<T> swatches, Func<T, PaletteColor> toColor) =>
         WithSwatches(swatches.Select(toColor));
-    
+
     public DocumentViewModelBuilder WithPalette(IEnumerable<PaletteColor> palette)
     {
         Palette = new(palette);
@@ -61,7 +64,7 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
 
         return this;
     }
-    
+
     public DocumentViewModelBuilder WithReferenceLayer(Action<ReferenceLayerBuilder> builder)
     {
         var reference = new ReferenceLayerBuilder();
@@ -69,30 +72,76 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
         builder(reference);
 
         ReferenceLayer = reference;
-        
+
         return this;
     }
     
+    public DocumentViewModelBuilder WithAnimationData(AnimationData? animationData, Folder documentRootFolder)
+    {
+        AnimationData = new List<KeyFrameBuilder>();
+
+        if (animationData != null && animationData.KeyFrameGroups.Count > 0)
+        {
+            BuildKeyFrames(animationData.KeyFrameGroups.Cast<IKeyFrame>().ToList(), AnimationData, documentRootFolder);
+        }
+
+        return this;
+    }
+
+    private static void BuildKeyFrames(List<IKeyFrame> root, List<KeyFrameBuilder> data, Folder documentRootFolder)
+    {
+        foreach (var keyFrame in root)
+        {
+            if (keyFrame is KeyFrameGroup group)
+            {
+                GroupKeyFrameBuilder builder = new GroupKeyFrameBuilder()
+                    .WithVisibility(group.Enabled)
+                    .WithId(group.LayerGuid)
+                    .WithLayerGuid(group.LayerGuid);
+
+                foreach (var child in group.Children)
+                {
+                    if(child is KeyFrameGroup childGroup)
+                    {
+                        builder.WithChild<GroupKeyFrameBuilder>(x => BuildKeyFrames(childGroup.Children, null, documentRootFolder));
+                    }
+                    else if (child is RasterKeyFrame rasterKeyFrame)
+                    {
+                        builder.WithChild<RasterKeyFrameBuilder>(x => x
+                            .WithVisibility(builder.IsVisible)
+                            .WithId(rasterKeyFrame.Guid)
+                            .WithLayerGuid(rasterKeyFrame.LayerGuid)
+                            .WithStartFrame(rasterKeyFrame.StartFrame)
+                            .WithDuration(rasterKeyFrame.Duration)
+                            .WithSurface(Surface.Load(rasterKeyFrame.ImageBytes)));
+                    }
+                }
+
+                data?.Add(builder);
+            }
+
+        }
+    }
+
     public abstract class StructureMemberBuilder
     {
         private MaskBuilder maskBuilder;
-        
+
         public int OrderInStructure { get; set; }
 
         public string Name { get; set; }
-        
+
         public bool IsVisible { get; set; }
-        
+
         public float Opacity { get; set; }
-        
+
         public BlendMode BlendMode { get; set; }
-        
+
         public bool ClipToMemberBelow { get; set; }
 
         public bool HasMask => maskBuilder is not null;
 
-        [NotNull]
-        public MaskBuilder Mask => maskBuilder ??= new MaskBuilder();
+        [NotNull] public MaskBuilder Mask => maskBuilder ??= new MaskBuilder();
 
         public Guid GuidValue { get; set; }
 
@@ -101,37 +150,37 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
             IsVisible = true;
             Opacity = 1;
         }
-        
+
         public StructureMemberBuilder WithOrderInStructure(int order)
         {
             OrderInStructure = order;
             return this;
         }
-        
+
         public StructureMemberBuilder WithName(string name)
         {
             Name = name;
             return this;
         }
-        
+
         public StructureMemberBuilder WithVisibility(bool visibility)
         {
             IsVisible = visibility;
             return this;
         }
-        
+
         public StructureMemberBuilder WithOpacity(float opacity)
         {
             Opacity = opacity;
             return this;
         }
-        
+
         public StructureMemberBuilder WithBlendMode(BlendMode blendMode)
         {
             BlendMode = blendMode;
             return this;
         }
-        
+
         public StructureMemberBuilder WithMask(Action<MaskBuilder> mask)
         {
             mask(Mask);
@@ -142,7 +191,7 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
         {
             return reference != null ? WithMask(x => mask(x, reference)) : this;
         }
-        
+
         public StructureMemberBuilder WithGuid(Guid guid)
         {
             GuidValue = guid;
@@ -160,7 +209,7 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
     {
         private int? width;
         private int? height;
-        
+
         public SurfaceBuilder? Surface { get; set; }
 
         public int Width
@@ -174,21 +223,21 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
             get => height ?? default;
             set => height = value;
         }
-        
+
         public int OffsetX { get; set; }
-        
+
         public int OffsetY { get; set; }
-        
+
         public bool LockAlpha { get; set; }
 
         public new LayerBuilder WithName(string name) => base.WithName(name) as LayerBuilder;
-        
+
         public new LayerBuilder WithVisibility(bool visibility) => base.WithVisibility(visibility) as LayerBuilder;
-        
+
         public new LayerBuilder WithOpacity(float opacity) => base.WithOpacity(opacity) as LayerBuilder;
-        
+
         public new LayerBuilder WithBlendMode(BlendMode blendMode) => base.WithBlendMode(blendMode) as LayerBuilder;
-        
+
         public new LayerBuilder WithClipToBelow(bool value) => base.WithClipToBelow(value) as LayerBuilder;
 
         public LayerBuilder WithLockAlpha(bool layerLockAlpha)
@@ -196,9 +245,9 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
             LockAlpha = layerLockAlpha;
             return this;
         }
-        
+
         public new LayerBuilder WithMask(Action<MaskBuilder> mask) => base.WithMask(mask) as LayerBuilder;
-        
+
         public new LayerBuilder WithGuid(Guid guid) => base.WithGuid(guid) as LayerBuilder;
 
         public LayerBuilder WithSurface(Surface surface)
@@ -215,7 +264,7 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
         }
 
         public LayerBuilder WithSize(VecI size) => WithSize(size.X, size.Y);
-        
+
         public LayerBuilder WithRect(int width, int height, int offsetX, int offsetY)
         {
             Width = width;
@@ -224,12 +273,13 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
             OffsetY = offsetY;
             return this;
         }
-        
+
         public LayerBuilder WithSurface(Action<SurfaceBuilder> surface)
         {
             if (width is null || height is null)
             {
-                throw new InvalidOperationException("You must first set the width and height of the layer. You can do this by calling WithRect() or setting the Width and Height properties.");
+                throw new InvalidOperationException(
+                    "You must first set the width and height of the layer. You can do this by calling WithRect() or setting the Width and Height properties.");
             }
 
             var surfaceBuilder = new SurfaceBuilder(new Surface(new VecI(Width, Height)));
@@ -244,15 +294,15 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
         public List<StructureMemberBuilder> Children { get; set; } = new List<StructureMemberBuilder>();
 
         public new FolderBuilder WithName(string name) => base.WithName(name) as FolderBuilder;
-        
+
         public new FolderBuilder WithVisibility(bool visibility) => base.WithVisibility(visibility) as FolderBuilder;
-        
+
         public new FolderBuilder WithOpacity(float opacity) => base.WithOpacity(opacity) as FolderBuilder;
-        
+
         public new FolderBuilder WithBlendMode(BlendMode blendMode) => base.WithBlendMode(blendMode) as FolderBuilder;
-        
+
         public new FolderBuilder WithMask(Action<MaskBuilder> mask) => base.WithMask(mask) as FolderBuilder;
-        
+
         public new FolderBuilder WithGuid(Guid guid) => base.WithGuid(guid) as FolderBuilder;
 
         public FolderBuilder WithClipToBelow(bool value) => base.WithClipToBelow(value) as FolderBuilder;
@@ -276,11 +326,11 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
         }
 
         public SurfaceBuilder WithImage(ReadOnlySpan<byte> buffer) => WithImage(buffer, 0, 0);
-        
+
         public SurfaceBuilder WithImage(ReadOnlySpan<byte> buffer, int x, int y)
         {
-            if(buffer.IsEmpty) return this;
-            
+            if (buffer.IsEmpty) return this;
+
             Surface.DrawingSurface.Canvas.DrawBitmap(Bitmap.Decode(buffer), x, y);
             return this;
         }
@@ -289,26 +339,26 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
     public class MaskBuilder
     {
         public bool IsVisible { get; set; }
-        
+
         public SurfaceBuilder Surface { get; set; }
 
         public MaskBuilder()
         {
             IsVisible = true;
         }
-        
+
         public MaskBuilder WithVisibility(bool isVisible)
         {
             IsVisible = isVisible;
             return this;
         }
-        
+
         public MaskBuilder WithSurface(Surface surface)
         {
             Surface = new SurfaceBuilder(surface);
             return this;
         }
-        
+
         public MaskBuilder WithSurface(int width, int height, Action<SurfaceBuilder> surface)
         {
             var surfaceBuilder = new SurfaceBuilder(new Surface(new VecI(Math.Max(width, 1), Math.Max(height, 1))));
@@ -321,13 +371,13 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
     public class ReferenceLayerBuilder
     {
         public bool IsVisible { get; set; }
-        
+
         public bool IsTopmost { get; set; }
-        
+
         public VecI ImageSize { get; set; }
-        
+
         public ShapeCorners Shape { get; set; }
-        
+
         public byte[] ImageBgra8888Bytes { get; set; }
 
         public ReferenceLayerBuilder WithIsVisible(bool isVisible)
@@ -346,7 +396,7 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
         {
             byte[] bytes = surface.ToByteArray();
             WithImage(surface.Size, bytes);
-            
+
             return this;
         }
 
@@ -361,22 +411,22 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
         {
             Shape = new ShapeCorners
             {
-                TopLeft = rect.TopLeft.ToVecD(), 
-                TopRight = rect.TopRight.ToVecD(), 
-                BottomLeft = rect.BottomLeft.ToVecD(), 
+                TopLeft = rect.TopLeft.ToVecD(),
+                TopRight = rect.TopRight.ToVecD(),
+                BottomLeft = rect.BottomLeft.ToVecD(),
                 BottomRight = rect.BottomRight.ToVecD()
             };
-            
+
             return this;
         }
     }
-    
 }
 
 internal class ChildrenBuilder
 {
-    public List<DocumentViewModelBuilder.StructureMemberBuilder> Children { get; set; } = new List<DocumentViewModelBuilder.StructureMemberBuilder>();
-        
+    public List<DocumentViewModelBuilder.StructureMemberBuilder> Children { get; set; } =
+        new List<DocumentViewModelBuilder.StructureMemberBuilder>();
+
     public ChildrenBuilder WithLayer(Action<DocumentViewModelBuilder.LayerBuilder> layer)
     {
         var layerBuilder = new DocumentViewModelBuilder.LayerBuilder();
@@ -384,7 +434,7 @@ internal class ChildrenBuilder
         Children.Add(layerBuilder);
         return this;
     }
-    
+
     public ChildrenBuilder WithFolder(Action<DocumentViewModelBuilder.FolderBuilder> folder)
     {
         var folderBuilder = new DocumentViewModelBuilder.FolderBuilder();
@@ -393,3 +443,78 @@ internal class ChildrenBuilder
         return this;
     }
 }
+
+internal class KeyFrameBuilder()
+{
+    public int StartFrame { get; set; }
+    public int Duration { get; set; }
+    public bool IsVisible { get; set; }
+    public Guid LayerGuid { get; set; }
+    public Guid Id { get; set; }
+
+    public KeyFrameBuilder WithStartFrame(int startFrame)
+    {
+        StartFrame = startFrame;
+        return this;
+    }
+
+    public KeyFrameBuilder WithDuration(int duration)
+    {
+        Duration = duration;
+        return this;
+    }
+
+    public KeyFrameBuilder WithVisibility(bool isVisible)
+    {
+        IsVisible = isVisible;
+        return this;
+    }
+
+    public KeyFrameBuilder WithLayerGuid(Guid layerGuid)
+    {
+        LayerGuid = layerGuid;
+        return this;
+    }
+
+    public KeyFrameBuilder WithId(Guid id)
+    {
+        Id = id;
+        return this;
+    }
+}
+
+internal class GroupKeyFrameBuilder : KeyFrameBuilder
+{
+    public List<KeyFrameBuilder> Children { get; set; } = new List<KeyFrameBuilder>();
+
+    public GroupKeyFrameBuilder WithChild<T>(Action<T> child) where T : KeyFrameBuilder, new()
+    {
+        var childBuilder = new T();
+        child(childBuilder);
+        Children.Add(childBuilder);
+        return this;
+    }
+    
+    public new GroupKeyFrameBuilder WithVisibility(bool isVisible) => base.WithVisibility(isVisible) as GroupKeyFrameBuilder;
+    public new GroupKeyFrameBuilder WithLayerGuid(Guid layerGuid) => base.WithLayerGuid(layerGuid) as GroupKeyFrameBuilder;
+    public new GroupKeyFrameBuilder WithId(Guid id) => base.WithId(id) as GroupKeyFrameBuilder;
+    public new GroupKeyFrameBuilder WithStartFrame(int startFrame) => base.WithStartFrame(startFrame) as GroupKeyFrameBuilder;
+    public new GroupKeyFrameBuilder WithDuration(int duration) => base.WithDuration(duration) as GroupKeyFrameBuilder;
+}
+
+internal class RasterKeyFrameBuilder : KeyFrameBuilder
+{
+    public DocumentViewModelBuilder.SurfaceBuilder Surface { get; set; }
+
+    public RasterKeyFrameBuilder WithSurface(Surface surface)
+    {
+        Surface = new DocumentViewModelBuilder.SurfaceBuilder(new Surface(surface));
+        return this;
+    }
+
+    public new RasterKeyFrameBuilder WithVisibility(bool isVisible) => base.WithVisibility(isVisible) as RasterKeyFrameBuilder;
+    public new RasterKeyFrameBuilder WithLayerGuid(Guid layerGuid) => base.WithLayerGuid(layerGuid) as RasterKeyFrameBuilder;
+    public new RasterKeyFrameBuilder WithId(Guid id) => base.WithId(id) as RasterKeyFrameBuilder;
+    public new RasterKeyFrameBuilder WithStartFrame(int startFrame) => base.WithStartFrame(startFrame) as RasterKeyFrameBuilder;
+    public new RasterKeyFrameBuilder WithDuration(int duration) => base.WithDuration(duration) as RasterKeyFrameBuilder;
+}

+ 5 - 2
src/PixiEditor.AvaloniaUI/Helpers/Extensions/PixiParserDocumentEx.cs

@@ -28,7 +28,8 @@ internal static class PixiParserDocumentEx
                     .WithIsVisible(r.Enabled)
                     .WithShape(r.Corners)
                     .WithIsTopmost(r.Topmost)
-                    .WithSurface(Surface.Load(r.ImageBytes)));
+                    .WithSurface(Surface.Load(r.ImageBytes)))
+                .WithAnimationData(document.AnimationData, document.RootFolder);
 
             BuildChildren(b, document.RootFolder.Children);
         });
@@ -65,6 +66,7 @@ internal static class PixiParserDocumentEx
         {
             builder
                 .WithName(layer.Name)
+                .WithGuid(layer.Guid)
                 .WithVisibility(layer.Enabled)
                 .WithOpacity(layer.Opacity)
                 .WithBlendMode((PixiEditor.ChangeableDocument.Enums.BlendMode)(int)layer.BlendMode)
@@ -75,11 +77,12 @@ internal static class PixiParserDocumentEx
                     (x, m) => x.WithVisibility(m.Enabled).WithSurface(m.Width, m.Height,
                         x => x.WithImage(m.ImageBytes, m.OffsetX, m.OffsetY)));
 
-            if (layer.Width > 0 && layer.Height > 0)
+            if (layer is { Width: > 0, Height: > 0 })
             {
                 builder.WithSurface(x => x.WithImage(layer.ImageBytes, 0, 0));
             }
         }
+        
     }
     
     public static SKBitmap RenderOldDocument(this SerializableDocument document)

+ 8 - 0
src/PixiEditor.AvaloniaUI/Models/Palettes/LocalPalettesFetcher.cs

@@ -203,6 +203,14 @@ internal class LocalPalettesFetcher : PaletteListDataSource
         Palette updated = null;
         string affectedFileName = null;
 
+        if (cachedPalettes == null)
+        {
+            await RefreshCacheAll();
+            return;
+        }
+        
+        
+
         switch (refreshType)
         {
             case RefreshType.All:

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

@@ -72,8 +72,8 @@
     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2"/>
     <PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
     <PackageReference Include="PixiEditor.ColorPicker.Models" Version="1.0.5"/>
-    <PackageReference Include="PixiEditor.Parser" Version="3.3.0"/>
-    <PackageReference Include="PixiEditor.Parser.Skia" Version="3.0.0"/>
+    <PackageReference Include="PixiEditor.Parser" Version="3.4.0"/>
+    <PackageReference Include="PixiEditor.Parser.Skia" Version="3.0.1"/>
     <PackageReference Include="PixiEditor.ColorPicker.AvaloniaUI" Version="1.0.5"/>
   </ItemGroup>
 

+ 65 - 1
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.Serialization.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers;
+using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.Models.IO.FileEncoders;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.DrawingApi.Core.Bridge;
@@ -16,6 +17,7 @@ using PixiEditor.Numerics;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Collections;
 using BlendMode = PixiEditor.Parser.BlendMode;
+using IKeyFrameChildrenContainer = PixiEditor.ChangeableDocument.Changeables.Interfaces.IKeyFrameChildrenContainer;
 using PixiDocument = PixiEditor.Parser.Document;
 
 namespace PixiEditor.AvaloniaUI.ViewModels.Document;
@@ -35,7 +37,8 @@ internal partial class DocumentViewModel
             Width = Width, Height = Height,
             Swatches = ToCollection(Swatches), Palette = ToCollection(Palette),
             RootFolder = root, PreviewImage = (TryRenderWholeImage().Value as Surface)?.DrawingSurface.Snapshot().Encode().AsSpan().ToArray(),
-            ReferenceLayer = GetReferenceLayer(doc)
+            ReferenceLayer = GetReferenceLayer(doc),
+            AnimationData = ToAnimationData(doc.AnimationData)
         };
 
         return document;
@@ -160,4 +163,65 @@ internal partial class DocumentViewModel
 
     private ColorCollection ToCollection(IList<PaletteColor> collection) =>
         new(collection.Select(x => Color.FromArgb(255, x.R, x.G, x.B)));
+
+    private AnimationData ToAnimationData(IReadOnlyAnimationData animationData)
+    {
+        var animData = new AnimationData();
+        animData.KeyFrameGroups = new List<KeyFrameGroup>();
+        BuildKeyFrames(animationData.KeyFrames, animData);
+        
+        return animData;
+    }
+
+    private static void BuildKeyFrames(IReadOnlyList<IReadOnlyKeyFrame> root, AnimationData animationData)
+    {
+        foreach (var keyFrame in root)
+        {
+            if(keyFrame is IKeyFrameChildrenContainer container)
+            {
+                KeyFrameGroup group = new();
+                group.LayerGuid = keyFrame.LayerGuid;
+                group.Enabled = keyFrame.IsVisible;
+                
+                foreach (var child in container.Children)
+                {
+                    if (child is IKeyFrameChildrenContainer groupKeyFrame)
+                    {
+                        BuildKeyFrames(groupKeyFrame.Children, null);
+                    }
+                    else if (child is IReadOnlyRasterKeyFrame rasterKeyFrame)
+                    {
+                        BuildRasterKeyFrame(rasterKeyFrame, group);
+                    }
+                }
+                
+                animationData?.KeyFrameGroups.Add(group);
+            }
+        }
+    }
+
+    private static void BuildRasterKeyFrame(IReadOnlyRasterKeyFrame rasterKeyFrame, KeyFrameGroup group)
+    {
+        var bounds = rasterKeyFrame.Image.FindChunkAlignedMostUpToDateBounds();
+
+        DrawingSurface surface = null;
+                        
+        if (bounds != null)
+        {
+            surface = DrawingBackendApi.Current.SurfaceImplementation.Create(
+                new ImageInfo(bounds.Value.Width, bounds.Value.Height));
+
+            rasterKeyFrame.Image.DrawMostUpToDateRegionOn(
+                new RectI(0, 0, bounds.Value.Width, bounds.Value.Height), ChunkResolution.Full, surface,
+                new VecI(0, 0));
+        }
+
+        group.Children.Add(new RasterKeyFrame()
+        {
+            LayerGuid = rasterKeyFrame.LayerGuid,
+            StartFrame = rasterKeyFrame.StartFrame,
+            Duration = rasterKeyFrame.Duration,
+            ImageBytes = surface?.Snapshot().Encode().AsSpan().ToArray(),
+        });
+    }
 }

+ 27 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/DocumentViewModel.cs

@@ -263,6 +263,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         viewModel.Palette = new ObservableRangeCollection<PaletteColor>(builderInstance.Palette);
 
         AddMembers(viewModel.StructureRoot.GuidValue, builderInstance.Children);
+        AddAnimationData(builderInstance.AnimationData);
 
         acc.AddFinishedActions(new DeleteRecordedChanges_Action());
         viewModel.MarkAsSaved();
@@ -342,6 +343,32 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                 AddMember(parentGuid, child);
             }
         }
+
+        void AddAnimationData(List<KeyFrameBuilder> data)
+        {
+            foreach (var keyFrame in data)
+            {
+                if (keyFrame is RasterKeyFrameBuilder rasterKeyFrameBuilder)
+                {
+                    acc.AddActions(
+                        new CreateRasterKeyFrame_Action(
+                            rasterKeyFrameBuilder.LayerGuid,
+                            rasterKeyFrameBuilder.StartFrame,
+                            false),
+                        new ActiveFrame_Action(rasterKeyFrameBuilder.StartFrame),
+                        new EndActiveFrame_Action());
+                    
+                    PasteImage(rasterKeyFrameBuilder.LayerGuid, rasterKeyFrameBuilder.Surface, rasterKeyFrameBuilder.Surface.Surface.Size.X,
+                        rasterKeyFrameBuilder.Surface.Surface.Size.Y, 0, 0, false);
+                    
+                    acc.AddFinishedActions();
+                }
+                else if(keyFrame is GroupKeyFrameBuilder groupKeyFrameBuilder)
+                {
+                    AddAnimationData(groupKeyFrameBuilder.Children);
+                }
+            }
+        }
     }
 
     public void MarkAsSaved()

+ 24 - 5
src/PixiEditor.AvaloniaUI/Views/Dialogs/ExportFilePopup.axaml

@@ -18,18 +18,37 @@
         <StackPanel HorizontalAlignment="Center" VerticalAlignment="Stretch" Orientation="Vertical"
                     Margin="0,15,0,0">
             <TabControl SelectedIndex="{Binding SelectedExportIndex, ElementName=saveFilePopup}">
+                <TabControl.Styles>
+                    <Style Selector="TabControl">
+                        <Setter Property="HorizontalAlignment" Value="Center" />
+                    </Style>
+                    <Style Selector="TabItem:selected">
+                        <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
+                    </Style>
+                </TabControl.Styles>
                 <TabControl.Items>
                     <TabItem IsSelected="True" ui1:Translator.Key="EXPORT_IMAGE_HEADER">
-
                     </TabItem>
                     <TabItem ui1:Translator.Key="EXPORT_ANIMATION_HEADER">
 
                     </TabItem>
                     <TabItem ui1:Translator.Key="EXPORT_SPRITESHEET_HEADER">
-                        <StackPanel>
-                            <input:NumberInput Min="0" Value="{Binding ElementName=saveFilePopup, Path=SpriteSheetRows, Mode=TwoWay}"/>
-                            <input:NumberInput Min="0" Value="{Binding ElementName=saveFilePopup, Path=SpriteSheetColumns, Mode=TwoWay}"/>
-                        </StackPanel>
+                        <Grid>
+                            <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="100" />
+                                <ColumnDefinition Width="50" />
+                            </Grid.ColumnDefinitions>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto" />
+                                <RowDefinition Height="Auto" />
+                            </Grid.RowDefinitions>
+                            <TextBlock ui1:Translator.Key="ROWS" Grid.Row="0" Grid.Column="0"/>
+                            <input:NumberInput Min="0" Width="50" Grid.Column="1" Grid.Row="0"
+                                               Value="{Binding ElementName=saveFilePopup, Path=SpriteSheetRows, Mode=TwoWay}" />
+                            <TextBlock ui1:Translator.Key="COLUMNS" Grid.Row="1" Grid.Column="0"/>
+                            <input:NumberInput Min="0" Width="50" Grid.Column="1" Grid.Row="1"
+                                               Value="{Binding ElementName=saveFilePopup, Path=SpriteSheetColumns, Mode=TwoWay}" />
+                        </Grid>
                     </TabItem>
                 </TabControl.Items>
             </TabControl>

+ 2 - 1
src/PixiEditor.AvaloniaUI/Views/Windows/PalettesBrowser.axaml.cs

@@ -186,6 +186,7 @@ internal partial class PalettesBrowser : PixiEditorPopup, IPopupWindow
     {
         localizationProvider = ViewModelMain.Current.LocalizationProvider;
         localizationProvider.OnLanguageChanged += LocalizationProviderOnOnLanguageChanged;
+        SortedResults = new ObservableRangeCollection<Palette>();
         MinWidth = DetermineWidth();
         
         PaletteProvider = vm.PaletteProvider;
@@ -244,7 +245,7 @@ internal partial class PalettesBrowser : PixiEditorPopup, IPopupWindow
 
     private bool CanDeletePalette(Palette palette)
     {
-        return palette != null && palette.Source.GetType() == typeof(LocalPalettesFetcher);
+        return palette != null && palette.Source != null && palette.Source.GetType() == typeof(LocalPalettesFetcher);
     }
 
     private void OnFavouritePalettesChanged(Setting<IEnumerable<string>> setting, IEnumerable<string> value)

+ 7 - 2
src/PixiEditor.ChangeableDocument/Changeables/Animations/GroupKeyFrame.cs

@@ -1,6 +1,9 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Animations;
+using System.Collections.Immutable;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
-internal class GroupKeyFrame : KeyFrame
+namespace PixiEditor.ChangeableDocument.Changeables.Animations;
+
+internal class GroupKeyFrame : KeyFrame, IKeyFrameChildrenContainer
 {
     private ChunkyImage originalLayerImage;
     private Document document;
@@ -9,6 +12,8 @@ internal class GroupKeyFrame : KeyFrame
     public override int Duration => Children.Count > 0 ? Children.Max(x => x.StartFrame + x.Duration) - StartFrame : 0;
     public override int StartFrame => Children.Count > 0 ? Children.Min(x => x.StartFrame) : 0;
 
+    IReadOnlyList<IReadOnlyKeyFrame> IKeyFrameChildrenContainer.Children => Children;
+
     public GroupKeyFrame(Guid layerGuid, int startFrame, Document document) : base(layerGuid, startFrame)
     {
         Id = layerGuid;

+ 6 - 2
src/PixiEditor.ChangeableDocument/Changeables/Animations/RasterKeyFrame.cs

@@ -1,10 +1,14 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
-internal class RasterKeyFrame : KeyFrame
+namespace PixiEditor.ChangeableDocument.Changeables.Animations;
+
+internal class RasterKeyFrame : KeyFrame, IReadOnlyRasterKeyFrame
 {
     public ChunkyImage Image { get; set; }
     public Document Document { get; set; }
     
+    IReadOnlyChunkyImage IReadOnlyRasterKeyFrame.Image => Image;
+    
     public RasterKeyFrame(Guid targetLayerGuid, int startFrame, Document document, ChunkyImage? cloneFrom = null)
         : base(targetLayerGuid, startFrame)
     {

+ 6 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IKeyFrameChildrenContainer.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+public interface IKeyFrameChildrenContainer
+{
+    public IReadOnlyList<IReadOnlyKeyFrame> Children { get; }
+}

+ 6 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyRasterKeyFrame.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+public interface IReadOnlyRasterKeyFrame : IReadOnlyKeyFrame
+{
+    public IReadOnlyChunkyImage Image { get; }
+}

+ 1 - 1
src/PixiEditor.DrawingApi.Skia/PixiEditor.DrawingApi.Skia.csproj

@@ -7,7 +7,7 @@
     </PropertyGroup>
 
     <ItemGroup>
-      <PackageReference Include="SkiaSharp" Version="2.88.6" />
+      <PackageReference Include="SkiaSharp" Version="2.88.8" />
     </ItemGroup>
 
     <ItemGroup>