Browse Source

Reference layer first working implementation

Equbuxu 2 years ago
parent
commit
0addb427e6
23 changed files with 510 additions and 151 deletions
  1. 3 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/DeleteReferenceLayer_ChangeInfo.cs
  2. 2 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/ReferenceLayerIsVisible_ChangeInfo.cs
  3. 6 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/SetReferenceLayer_ChangeInfo.cs
  4. 3 0
      src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/TransformReferenceLayer_ChangeInfo.cs
  5. 0 3
      src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateReferenceLayer_ChangeInfo.cs
  6. 6 3
      src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyReferenceLayer.cs
  7. 15 6
      src/PixiEditor.ChangeableDocument/Changeables/ReferenceLayer.cs
  8. 0 51
      src/PixiEditor.ChangeableDocument/Changes/Root/CreateReferenceLayer_UpdateableChange.cs
  9. 36 0
      src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/DeleteReferenceLayer_Change.cs
  10. 45 0
      src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/ReferenceLayerIsVisible_Change.cs
  11. 47 0
      src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/SetReferenceLayer_Change.cs
  12. 48 0
      src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/TransformReferenceLayer_UpdateableChange.cs
  13. 15 0
      src/PixiEditor/Helpers/WriteableBitmapHelpers.cs
  14. 33 11
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  15. 20 1
      src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs
  16. 6 18
      src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs
  17. 103 0
      src/PixiEditor/ViewModels/SubViewModels/Document/ReferenceLayerViewModel.cs
  18. 75 1
      src/PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs
  19. 1 0
      src/PixiEditor/Views/UserControls/Layers/LayersManager.xaml
  20. 31 12
      src/PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml
  21. 8 39
      src/PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml.cs
  22. 5 4
      src/PixiEditor/Views/UserControls/Viewport.xaml
  23. 2 2
      src/PixiEditor/Views/UserControls/Viewport.xaml.cs

+ 3 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/DeleteReferenceLayer_ChangeInfo.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
+
+public record class DeleteReferenceLayer_ChangeInfo : IChangeInfo;

+ 2 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/ReferenceLayerIsVisible_ChangeInfo.cs

@@ -0,0 +1,2 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
+public record class ReferenceLayerIsVisible_ChangeInfo(bool IsVisible) : IChangeInfo;

+ 6 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/SetReferenceLayer_ChangeInfo.cs

@@ -0,0 +1,6 @@
+using System.Collections.Immutable;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Structure;
+
+public record class SetReferenceLayer_ChangeInfo(ImmutableArray<byte> ImagePbgra32Bytes, VecI ImageSize, ShapeCorners Shape) : IChangeInfo;

+ 3 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/Root/ReferenceLayerChangeInfos/TransformReferenceLayer_ChangeInfo.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
+
+public record class TransformReferenceLayer_ChangeInfo(ShapeCorners Corners) : IChangeInfo;

+ 0 - 3
src/PixiEditor.ChangeableDocument/ChangeInfos/Structure/CreateReferenceLayer_ChangeInfo.cs

@@ -1,3 +0,0 @@
-namespace PixiEditor.ChangeableDocument.ChangeInfos.Structure;
-
-public record class CreateReferenceLayer_ChangeInfo(bool ShapeOnly) : IChangeInfo;

+ 6 - 3
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyReferenceLayer.cs

@@ -1,8 +1,11 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using System.Collections.Immutable;
+using PixiEditor.DrawingApi.Core.Numerics;
 
+namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 public interface IReadOnlyReferenceLayer
 {
-    public Surface Image { get; }
-
+    public ImmutableArray<byte> ImagePbgra32Bytes { get; }
+    public VecI ImageSize { get; }
     public ShapeCorners Shape { get; }
+    public bool IsVisible { get; }
 }

+ 15 - 6
src/PixiEditor.ChangeableDocument/Changeables/ReferenceLayer.cs

@@ -1,16 +1,25 @@
-using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using System.Collections.Immutable;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changeables;
 
 public class ReferenceLayer : IReadOnlyReferenceLayer
 {
-    public Surface Image { get; }
-    
+    public ImmutableArray<byte> ImagePbgra32Bytes { get; }
+    public VecI ImageSize { get; }
     public ShapeCorners Shape { get; set; }
-
-    public ReferenceLayer(Surface image, ShapeCorners shape)
+    public bool IsVisible { get; set; } = true;
+    
+    public ReferenceLayer(ImmutableArray<byte> imagePbgra32Bytes, VecI imageSize, ShapeCorners shape)
     {
-        Image = image;
+        ImagePbgra32Bytes = imagePbgra32Bytes;
+        ImageSize = imageSize;
         Shape = shape;
     }
+
+    public ReferenceLayer Clone()
+    {
+        return new ReferenceLayer(ImagePbgra32Bytes, ImageSize, Shape);
+    }
 }

+ 0 - 51
src/PixiEditor.ChangeableDocument/Changes/Root/CreateReferenceLayer_UpdateableChange.cs

@@ -1,51 +0,0 @@
-using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
-
-namespace PixiEditor.ChangeableDocument.Changes.Root;
-
-internal class CreateReferenceLayer_UpdateableChange : UpdateableChange
-{
-    private readonly Surface? surface;
-    private ShapeCorners shape;
-
-    [GenerateUpdateableChangeActions]
-    public CreateReferenceLayer_UpdateableChange(Surface? surface, ShapeCorners shape)
-    {
-        this.surface = surface;
-        this.shape = shape;
-    }
-
-    [UpdateChangeMethod]
-    public void Update(ShapeCorners shape)
-    {
-        this.shape = shape;
-    }
-
-    public override bool InitializeAndValidate(Document target)
-    {
-        if (surface is null)
-        {
-            return false;
-        }
-
-        target.ReferenceLayer = new ReferenceLayer(surface!, shape);
-        return true;
-    }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
-    {
-        target.ReferenceLayer!.Shape = shape;
-        ignoreInUndo = true;
-        return new CreateReferenceLayer_ChangeInfo(true);
-    }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
-    {
-        throw new NotImplementedException();
-    }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
-    {
-        target.ReferenceLayer!.Shape = shape;
-        return new CreateReferenceLayer_ChangeInfo(true);
-    }
-}

+ 36 - 0
src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/DeleteReferenceLayer_Change.cs

@@ -0,0 +1,36 @@
+using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
+using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
+
+namespace PixiEditor.ChangeableDocument.Changes.Root.ReferenceLayerChanges;
+
+internal class DeleteReferenceLayer_Change : Change
+{
+    private ReferenceLayer? lastReferenceLayer;
+
+    [GenerateMakeChangeAction]
+    public DeleteReferenceLayer_Change() { }
+    
+    public override bool InitializeAndValidate(Document target)
+    {
+        if (target.ReferenceLayer is null)
+            return false;
+        lastReferenceLayer = target.ReferenceLayer.Clone();
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        target.ReferenceLayer = null;
+        ignoreInUndo = false;
+        return new DeleteReferenceLayer_ChangeInfo();
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        target.ReferenceLayer = lastReferenceLayer!.Clone();
+        return new SetReferenceLayer_ChangeInfo(
+            target.ReferenceLayer.ImagePbgra32Bytes,
+            target.ReferenceLayer.ImageSize,
+            target.ReferenceLayer.Shape);
+    }
+}

+ 45 - 0
src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/ReferenceLayerIsVisible_Change.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
+
+namespace PixiEditor.ChangeableDocument.Changes.Root.ReferenceLayerChanges;
+internal class ReferenceLayerIsVisible_Change : Change
+{
+    private readonly bool isVisible;
+    private bool oldIsVisible;
+
+    [GenerateMakeChangeAction]
+    public ReferenceLayerIsVisible_Change(bool isVisible)
+    {
+        this.isVisible = isVisible;
+    }
+
+    public override bool InitializeAndValidate(Document target)
+    {
+        if (target.ReferenceLayer is null)
+            return false;
+        oldIsVisible = target.ReferenceLayer.IsVisible;
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        if (oldIsVisible == isVisible)
+        {
+            ignoreInUndo = true;
+            return new None();
+        }
+        ignoreInUndo = false;
+        target.ReferenceLayer!.IsVisible = isVisible;
+        return new ReferenceLayerIsVisible_ChangeInfo(isVisible);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        target.ReferenceLayer!.IsVisible = oldIsVisible;
+        return new ReferenceLayerIsVisible_ChangeInfo(isVisible);
+    }
+}

+ 47 - 0
src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/SetReferenceLayer_Change.cs

@@ -0,0 +1,47 @@
+using System.Collections.Immutable;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
+using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changes.Root.ReferenceLayerChanges;
+
+internal class SetReferenceLayer_Change : Change
+{
+    private readonly ImmutableArray<byte> imagePbgra32Bytes;
+    private readonly VecI imageSize;
+    private readonly ShapeCorners shape;
+
+    private ReferenceLayer? lastReferenceLayer;
+    
+    [GenerateMakeChangeAction]
+    public SetReferenceLayer_Change(ShapeCorners shape, ImmutableArray<byte> imagePbgra32Bytes, VecI imageSize)
+    {
+        this.imagePbgra32Bytes = imagePbgra32Bytes;
+        this.imageSize = imageSize;
+        this.shape = shape;
+    }
+
+    public override bool InitializeAndValidate(Document target)
+    {
+        lastReferenceLayer = target.ReferenceLayer?.Clone();
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        target.ReferenceLayer = new ReferenceLayer(imagePbgra32Bytes, imageSize, shape);
+        ignoreInUndo = false;
+        return new SetReferenceLayer_ChangeInfo(imagePbgra32Bytes, imageSize, shape);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        target.ReferenceLayer = lastReferenceLayer?.Clone();
+        if (lastReferenceLayer is null)
+            return new DeleteReferenceLayer_ChangeInfo();
+        return new SetReferenceLayer_ChangeInfo(
+            lastReferenceLayer.ImagePbgra32Bytes,
+            lastReferenceLayer.ImageSize,
+            lastReferenceLayer.Shape);
+    }
+}

+ 48 - 0
src/PixiEditor.ChangeableDocument/Changes/Root/ReferenceLayerChanges/TransformReferenceLayer_UpdateableChange.cs

@@ -0,0 +1,48 @@
+using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
+
+namespace PixiEditor.ChangeableDocument.Changes.Root.ReferenceLayerChanges;
+
+internal class TransformReferenceLayer_UpdateableChange : UpdateableChange
+{
+    private ShapeCorners originalCorners;
+    private ShapeCorners corners;
+
+    [GenerateUpdateableChangeActions]
+    public TransformReferenceLayer_UpdateableChange(ShapeCorners corners)
+    {
+        this.corners = corners;
+    }
+
+    [UpdateChangeMethod]
+    public void Update(ShapeCorners corners)
+    {
+        this.corners = corners;
+    }
+    
+    public override bool InitializeAndValidate(Document target)
+    {
+        if (target.ReferenceLayer is null)
+            return false;
+        originalCorners = target.ReferenceLayer.Shape;
+        return true;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
+    {
+        target.ReferenceLayer!.Shape = corners;
+        return new TransformReferenceLayer_ChangeInfo(corners);
+    }
+    
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        target.ReferenceLayer!.Shape = corners;
+        ignoreInUndo = false;
+        return new TransformReferenceLayer_ChangeInfo(corners);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        target.ReferenceLayer!.Shape = originalCorners;
+        return new TransformReferenceLayer_ChangeInfo(originalCorners);
+    }
+}

+ 15 - 0
src/PixiEditor/Helpers/WriteableBitmapHelpers.cs

@@ -0,0 +1,15 @@
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.Helpers;
+
+internal static class WriteableBitmapHelpers
+{
+    public static WriteableBitmap FromPbgra32Array(byte[] pbgra32pixels, VecI size)
+    {
+        WriteableBitmap bitmap = new WriteableBitmap(size.X, size.Y, 96, 96, PixelFormats.Pbgra32, null);
+        bitmap.WritePixels(new(0, 0, size.X, size.Y), pbgra32pixels, bitmap.BackBufferStride, 0);
+        return bitmap;
+    }
+}

+ 33 - 11
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -5,6 +5,7 @@ using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos.Drawing;
 using PixiEditor.ChangeableDocument.ChangeInfos.Properties;
 using PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
@@ -14,6 +15,7 @@ using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.Enums;
 using PixiEditor.ViewModels.SubViewModels.Document;
+using TransformReferenceLayer_ChangeInfo = PixiEditor.ChangeableDocument.ChangeInfos.Root.ReferenceLayerChangeInfos.TransformReferenceLayer_ChangeInfo;
 
 namespace PixiEditor.Models.DocumentModels;
 #nullable enable
@@ -97,8 +99,17 @@ internal class DocumentUpdater
             case StructureMemberMaskIsVisible_ChangeInfo info:
                 ProcessMaskIsVisible(info);
                 break;
-            case CreateReferenceLayer_ChangeInfo info:
-                ProcessCreateReferenceLayer(info);
+            case SetReferenceLayer_ChangeInfo info:
+                ProcessSetReferenceLayer(info);
+                break;
+            case DeleteReferenceLayer_ChangeInfo info:
+                ProcessDeleteReferenceLayer(info);
+                break;
+            case TransformReferenceLayer_ChangeInfo info:
+                ProcessTransformReferenceLayer(info);
+                break;
+            case ReferenceLayerIsVisible_ChangeInfo info:
+                ProcessReferenceLayerIsVisible(info);
                 break;
             case SetSelectedMember_PassthroughAction info:
                 ProcessSetSelectedMember(info);
@@ -115,6 +126,26 @@ internal class DocumentUpdater
         }
     }
 
+    private void ProcessReferenceLayerIsVisible(ReferenceLayerIsVisible_ChangeInfo info)
+    {
+        doc.ReferenceLayerViewModel.InternalSetReferenceLayerIsVisible(info.IsVisible);
+    }
+
+    private void ProcessTransformReferenceLayer(TransformReferenceLayer_ChangeInfo info)
+    {
+        doc.ReferenceLayerViewModel.InternalTransformReferenceLayer(info.Corners);
+    }
+
+    private void ProcessDeleteReferenceLayer(DeleteReferenceLayer_ChangeInfo info)
+    {
+        doc.ReferenceLayerViewModel.InternalDeleteReferenceLayer();
+    }
+
+    private void ProcessSetReferenceLayer(SetReferenceLayer_ChangeInfo info)
+    {
+        doc.ReferenceLayerViewModel.InternalSetReferenceLayer(info.ImagePbgra32Bytes, info.ImageSize, info.Shape);
+    }
+    
     private void ProcessRemoveSoftSelectedMember(RemoveSoftSelectedMember_PassthroughAction info)
     {
         StructureMemberViewModel? member = doc.StructureHelper.Find(info.GuidValue);
@@ -162,15 +193,6 @@ internal class DocumentUpdater
         doc.InternalSetSelectedMember(member);
     }
 
-    private void ProcessCreateReferenceLayer(CreateReferenceLayer_ChangeInfo info)
-    {
-        doc.RaisePropertyChanged(nameof(doc.ReferenceLayer));
-        doc.RaisePropertyChanged(nameof(doc.ReferenceBitmap));
-        doc.RaisePropertyChanged(nameof(doc.ReferenceBitmapSize));
-        doc.RaisePropertyChanged(nameof(doc.ReferenceTransformMatrix));
-        doc.RaisePropertyChanged(nameof(doc.ReferenceShape));
-    }
-
     private void ProcessMaskIsVisible(StructureMemberMaskIsVisible_ChangeInfo info)
     {
         StructureMemberViewModel? member = doc.StructureHelper.FindOrThrow(info.GuidValue);

+ 20 - 1
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -1,5 +1,7 @@
-using ChunkyImageLib;
+using System.Collections.Immutable;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
@@ -8,6 +10,7 @@ using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 using PixiEditor.Models.DocumentPassthroughActions;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Position;
+using PixiEditor.Parser;
 using PixiEditor.ViewModels.SubViewModels.Document;
 
 namespace PixiEditor.Models.DocumentModels.Public;
@@ -259,4 +262,20 @@ internal class DocumentOperationsModule
 
         Internals.ActionAccumulator.AddFinishedActions(new CenterContent_Action(structureMembers.ToList()));
     }
+
+    public void ImportReferenceLayer(ImmutableArray<byte> imagePbgra32Bytes, VecI imageSize, ShapeCorners corners)
+    {
+        if (Internals.ChangeController.IsChangeActive)
+            return;
+
+        Internals.ActionAccumulator.AddFinishedActions(new SetReferenceLayer_Action(corners, imagePbgra32Bytes, imageSize));
+    }
+
+    public void DeleteReferenceLayer()
+    {
+        if (Internals.ChangeController.IsChangeActive || Document.ReferenceLayerViewModel.ReferenceBitmap is null)
+            return;
+
+        Internals.ActionAccumulator.AddFinishedActions(new DeleteReferenceLayer_Action());
+    }
 }

+ 6 - 18
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -1,4 +1,5 @@
-using System.IO;
+using System.Collections.Immutable;
+using System.IO;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using ChunkyImageLib;
@@ -111,20 +112,6 @@ internal partial class DocumentViewModel : NotifyableObject
     public bool UpdateableChangeActive => Internals.ChangeController.IsChangeActive;
     public bool HasSavedUndo => Internals.Tracker.HasSavedUndo;
     public bool HasSavedRedo => Internals.Tracker.HasSavedRedo;
-    public IReadOnlyReferenceLayer? ReferenceLayer => Internals.Tracker.Document.ReferenceLayer;
-    public BitmapSource? ReferenceBitmap => ReferenceLayer?.Image.ToWriteableBitmap();
-    public VecI ReferenceBitmapSize => ReferenceLayer?.Image.Size ?? VecI.Zero;
-    public ShapeCorners ReferenceShape => ReferenceLayer?.Shape ?? default;
-    public Matrix ReferenceTransformMatrix
-    {
-        get
-        {
-            if (ReferenceLayer is null)
-                return Matrix.Identity;
-            Matrix3X3 skiaMatrix = OperationHelper.CreateMatrixFromPoints(ReferenceLayer.Shape, ReferenceLayer.Image.Size);
-            return new Matrix(skiaMatrix.ScaleX, skiaMatrix.SkewY, skiaMatrix.SkewX, skiaMatrix.ScaleY, skiaMatrix.TransX, skiaMatrix.TransY);
-        }
-    }
 
     public FolderViewModel StructureRoot { get; }
     public DocumentStructureModule StructureHelper { get; }
@@ -153,7 +140,7 @@ internal partial class DocumentViewModel : NotifyableObject
     public WpfObservableRangeCollection<Color> Palette { get; set; } = new WpfObservableRangeCollection<Color>();
 
     public DocumentTransformViewModel TransformViewModel { get; }
-
+    public ReferenceLayerViewModel ReferenceLayerViewModel { get; }
 
     private DocumentInternalParts Internals { get; }
 
@@ -181,6 +168,8 @@ internal partial class DocumentViewModel : NotifyableObject
         VecI previewSize = StructureMemberViewModel.CalculatePreviewSize(SizeBindable);
         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);
+
+        ReferenceLayerViewModel = new(this, Internals);
     }
 
     public static DocumentViewModel Build(Action<DocumentViewModelBuilder> builder)
@@ -335,7 +324,7 @@ internal partial class DocumentViewModel : NotifyableObject
         // there is a tiny chance that the image might get disposed by another thread
         try
         {
-            // it might've been a better idea to implement this function asynchonously
+            // it might've been a better idea to implement this function asynchronously
             // via a passthrough action to avoid all the try catches
             if (fromAllLayers)
             {
@@ -422,6 +411,5 @@ internal partial class DocumentViewModel : NotifyableObject
 
     public void InternalAddSoftSelectedMember(StructureMemberViewModel member) => softSelectedStructureMembers.Add(member);
     public void InternalRemoveSoftSelectedMember(StructureMemberViewModel member) => softSelectedStructureMembers.Remove(member);
-
     #endregion
 }

+ 103 - 0
src/PixiEditor/ViewModels/SubViewModels/Document/ReferenceLayerViewModel.cs

@@ -0,0 +1,103 @@
+using System.Collections.Immutable;
+using System.ComponentModel;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using ChunkyImageLib.DataHolders;
+using ChunkyImageLib.Operations;
+using PixiEditor.ChangeableDocument.Actions.Generated;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Helpers;
+using PixiEditor.Models.DocumentModels;
+
+namespace PixiEditor.ViewModels.SubViewModels.Document;
+
+#nullable enable
+internal class ReferenceLayerViewModel : INotifyPropertyChanged
+{
+    private readonly DocumentViewModel doc;
+    private readonly DocumentInternalParts internals;
+    public event PropertyChangedEventHandler PropertyChanged;
+    
+    public WriteableBitmap? ReferenceBitmap { get; private set; }
+
+    private ShapeCorners referenceShape;
+    public ShapeCorners ReferenceShapeBindable 
+    { 
+        get => referenceShape; 
+        set
+        {
+            if (!doc.UpdateableChangeActive)
+                internals.ActionAccumulator.AddFinishedActions(new TransformReferenceLayer_Action(value));
+        }
+    }
+    
+    public Matrix ReferenceTransformMatrix
+    {
+        get
+        {
+            if (ReferenceBitmap is null)
+                return Matrix.Identity;
+            Matrix3X3 skiaMatrix = OperationHelper.CreateMatrixFromPoints((ShapeCorners)ReferenceShapeBindable, new VecD(ReferenceBitmap.Width, ReferenceBitmap.Height));
+            return new Matrix(skiaMatrix.ScaleX, skiaMatrix.SkewY, skiaMatrix.SkewX, skiaMatrix.ScaleY, skiaMatrix.TransX, skiaMatrix.TransY);
+        }
+    }
+
+    private bool isVisible;
+    public bool IsVisibleBindable
+    {
+        get => isVisible;
+        set
+        {
+            if (!doc.UpdateableChangeActive)
+                internals.ActionAccumulator.AddFinishedActions(new ReferenceLayerIsVisible_Action(value));
+        }
+    }
+
+    public ReferenceLayerViewModel(DocumentViewModel doc, DocumentInternalParts internals)
+    {
+        this.doc = doc;
+        this.internals = internals;
+    }
+
+    private void RaisePropertyChanged(string name)
+    {
+        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+    }
+    
+    #region Internal methods
+
+    public void InternalSetReferenceLayer(ImmutableArray<byte> imagePbgra32Bytes, VecI imageSize, ShapeCorners shape)
+    {
+        ReferenceBitmap = WriteableBitmapHelpers.FromPbgra32Array(imagePbgra32Bytes.ToArray(), imageSize);
+        referenceShape = shape;
+        isVisible = true;
+        RaisePropertyChanged(nameof(ReferenceBitmap));
+        RaisePropertyChanged(nameof(ReferenceShapeBindable));
+        RaisePropertyChanged(nameof(ReferenceTransformMatrix));
+        RaisePropertyChanged(nameof(IsVisibleBindable));
+    }
+
+    public void InternalDeleteReferenceLayer()
+    {
+        ReferenceBitmap = null;
+        isVisible = false;
+        RaisePropertyChanged(nameof(ReferenceBitmap));
+        RaisePropertyChanged(nameof(ReferenceTransformMatrix));
+        RaisePropertyChanged(nameof(IsVisibleBindable));
+    }
+    
+    public void InternalTransformReferenceLayer(ShapeCorners shape)
+    {
+        referenceShape = shape;
+        RaisePropertyChanged(nameof(ReferenceShapeBindable));
+        RaisePropertyChanged(nameof(ReferenceTransformMatrix));
+    }
+
+    public void InternalSetReferenceLayerIsVisible(bool isVisible)
+    {
+        this.isVisible = isVisible;
+        RaisePropertyChanged(nameof(IsVisibleBindable));
+    }
+
+    #endregion
+}

+ 75 - 1
src/PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -1,8 +1,17 @@
-using System.Windows.Input;
+using System.Collections.Immutable;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media.Imaging;
+using ChunkyImageLib.DataHolders;
+using Microsoft.Win32;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Enums;
+using PixiEditor.Models.IO;
 using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.Views.Dialogs;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main;
 #nullable enable
@@ -267,4 +276,69 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
 
     [Command.Basic("PixiEditor.Layer.MergeWithBelow", "Merge selected layer with the one below it", "Merge selected layer with the one below it", CanExecute = "PixiEditor.Layer.HasMemberBelow")]
     public void MergeWithBelow() => MergeSelectedWith(false);
+
+    [Evaluator.CanExecute("PixiEditor.Layer.ReferenceLayerExists")]
+    public bool ReferenceLayerExists() => Owner.DocumentManagerSubViewModel.ActiveDocument?.ReferenceLayerViewModel.ReferenceBitmap is not null;
+    [Evaluator.CanExecute("PixiEditor.Layer.ReferenceLayerDoesntExist")]
+    public bool ReferenceLayerDoesntExist() => 
+        Owner.DocumentManagerSubViewModel.ActiveDocument is null ? false : Owner.DocumentManagerSubViewModel.ActiveDocument.ReferenceLayerViewModel.ReferenceBitmap is null;
+
+    [Command.Basic("PixiEditor.Layer.ImportReferenceLayer", "Import reference layer", "Import reference layer", "PixiEditor.Layer.ReferenceLayerDoesntExist")]
+    public void ImportReferenceLayer()
+    {
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (doc is null)
+            return;
+
+        string path = OpenReferenceLayerFilePicker();
+        if (path is null)
+            return;
+
+        WriteableBitmap bitmap;
+        try
+        {
+            bitmap = Importer.ImportWriteableBitmap(path);
+        }
+        catch (Exception e)
+        {
+            NoticeDialog.Show("Error while importing the image", "Error");
+            return;
+        }
+
+        byte[] pixels = new byte[bitmap.PixelWidth * bitmap.PixelHeight * 4];
+        bitmap.CopyPixels(pixels, bitmap.PixelWidth * 4, 0);
+
+        VecI size = new VecI(bitmap.PixelWidth, bitmap.PixelHeight);
+
+        RectD referenceImageRect = new RectD(VecD.Zero, doc.SizeBindable).AspectFit(new RectD(VecD.Zero, size));
+        ShapeCorners corners = new ShapeCorners(referenceImageRect);
+
+        doc.Operations.ImportReferenceLayer(
+            pixels.ToImmutableArray(), 
+            size, 
+            corners);
+    }
+
+    [Command.Basic("PixiEditor.Layer.DeleteReferenceLayer", "Delete reference layer", "Delete reference layer", "PixiEditor.Layer.ReferenceLayerExists")]
+    public void DeleteReferenceLayer()
+    {
+        var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+        if (doc is null)
+            return;
+
+        doc.Operations.DeleteReferenceLayer();
+    }
+
+    private string OpenReferenceLayerFilePicker()
+    {
+        var imagesFilter = new FileTypeDialogDataSet(FileTypeDialogDataSet.SetKind.Images).GetFormattedTypes();
+        OpenFileDialog dialog = new OpenFileDialog
+        {
+            Title = "Reference layer path",
+            CheckPathExists = true,
+            Filter = imagesFilter
+        };
+
+        return (bool)dialog.ShowDialog() ? dialog.FileName : null;
+    }
 }

+ 1 - 0
src/PixiEditor/Views/UserControls/Layers/LayersManager.xaml

@@ -137,6 +137,7 @@
         <DockPanel LastChildFill="True" Grid.Row="2" Margin="0, -12, 0, 0">
             <layerUserControls:ReferenceLayer
                 DockPanel.Dock="Bottom"
+                Document="{Binding Path=ActiveDocument, ElementName=layersManager}"
                 Visibility="{Binding Path=ActiveDocument, ElementName=layersManager, Converter={converters:NotNullToVisibilityConverter}}" 
                 Background="{StaticResource MainColor}" 
                 Grid.Row="3" VerticalAlignment="Bottom"/>

+ 31 - 12
src/PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml

@@ -3,48 +3,67 @@
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-             xmlns:local="clr-namespace:PixiEditor.Views.UserControls.Layers" xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours" xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:local1="clr-namespace:PixiEditor.Views.UserControls" xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:local="clr-namespace:PixiEditor.Views.UserControls.Layers" 
+             xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours" 
+             xmlns:i="http://schemas.microsoft.com/xaml/behaviors" 
+             xmlns:local1="clr-namespace:PixiEditor.Views.UserControls" 
+             xmlns:cmds="clr-namespace:PixiEditor.Models.Commands.XAML"
+             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
              mc:Ignorable="d" 
              d:DesignHeight="60" d:DesignWidth="350" VerticalAlignment="Center" Name="uc">
     <Border BorderBrush="{StaticResource DarkerAccentColor}" BorderThickness="0 2 0 0" MinWidth="60" Focusable="True">
         <i:Interaction.Behaviors>
             <behaviors:ClearFocusOnClickBehavior/>
         </i:Interaction.Behaviors>
-        <!--<Grid>
+        <Grid>
             <Grid Background="Transparent"/>
             <Grid Grid.Row="0"  VerticalAlignment="Center">
                 <Grid.ColumnDefinitions>
                     <ColumnDefinition Width="30"/>
                     <ColumnDefinition Width="*"/>
                 </Grid.ColumnDefinitions>
-                <Grid Visibility="{Binding Layer, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"  Grid.ColumnSpan="2" Grid.RowSpan="2" Panel.ZIndex="5">
-                    <Grid MouseDown="Grid_MouseDown" Cursor="Hand" Visibility="{Binding ElementName=visibilityCheckbox, Path=IsChecked, Converter={InverseBoolToVisibilityConverter}}"  Background="Transparent"/>
+                <Grid Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"  Grid.ColumnSpan="2" Grid.RowSpan="2" Panel.ZIndex="5">
+                    <Grid Cursor="Hand" Visibility="{Binding ElementName=visibilityCheckbox, Path=IsChecked, Converter={InverseBoolToVisibilityConverter}}"  Background="Transparent">
+                        <i:Interaction.Triggers>
+                            <i:EventTrigger EventName="MouseUp">
+                                <i:InvokeCommandAction Command="{cmds:Command PixiEditor.Layer.ImportReferenceLayer}"
+                                    PassEventArgsToCommand="True"/>
+                            </i:EventTrigger>
+                        </i:Interaction.Triggers>
+                    </Grid>
                 </Grid>
                 <Grid Grid.Column="0" Height="16" Name="layerVisibilityCheckboxGrid">
                     <CheckBox 
-                        Visibility="{Binding Layer, 
+                        Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, 
                                              ElementName=uc, 
                                              Converter={converters:NotNullToVisibilityConverter}}"
                         Style="{StaticResource ImageCheckBox}" VerticalAlignment="Center"
                         IsThreeState="False" HorizontalAlignment="Center" 
-                        IsChecked="{Binding Path=Layer.IsVisible, Mode=TwoWay, ElementName=uc}"/>
+                        IsChecked="{Binding Path=Document.ReferenceLayerViewModel.IsVisibleBindable, Mode=TwoWay, ElementName=uc}"/>
                 </Grid>
                 <StackPanel Name="middleStackPanel" Height="40" Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Center">
                     <Border HorizontalAlignment="Left" 
-                            Visibility="{Binding Layer, ElementName=uc, Converter={converters:NotNullToVisibilityConverter}}" 
+                            Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NotNullToVisibilityConverter}}" 
                             Width="30" Height="30"
                             BorderThickness="1" BorderBrush="Black"
                             Background="{StaticResource MainColor}"
                             Margin="5, 0, 10, 0">
+                        <Image Source="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap,ElementName=uc}" Stretch="Uniform" Width="26" Height="26"
+                           RenderOptions.BitmapScalingMode="HighQuality" IsHitTestVisible="False"/>
                     </Border>
-                    <Image Margin="0 0 5 0" Width="20" Source="/Images/Layer-add.png"  Visibility="{Binding Layer, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"/>
+                    <Image Margin="0 0 5 0" Width="20" Source="/Images/Layer-add.png"  
+                           Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"/>
 
                     <local1:PrependTextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}" 
                                              Margin="0 0 5 0" Prepend="Add " Foreground="White" 
-                                             HidePrepend="{Binding Layer, ElementName=uc, Converter={converters:NotNullToBoolConverter}}"
+                                             HidePrepend="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NotNullToBoolConverter}}"
                                              FontSize="15" VerticalAlignment="Center" Text="Reference Layer" />
-                    <Button Click="TrashButton_Click" Cursor="Hand" Grid.Column="1" Visibility="{Binding Layer, ElementName=uc, Converter={BoolToVisibilityConverter}}" Style="{StaticResource ImageButtonStyle}" Width="20" Height="20" HorizontalAlignment="Right">
+                    <Button Cursor="Hand" Grid.Column="1" 
+                            Command="{cmds:Command PixiEditor.Layer.DeleteReferenceLayer}"
+                            Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={BoolToVisibilityConverter}}" 
+                            Style="{StaticResource ImageButtonStyle}" 
+                            Width="20" Height="20" HorizontalAlignment="Right">
                         <Button.Background>
                             <ImageBrush ImageSource="/Images/Trash.png"/>
                         </Button.Background>
@@ -93,6 +112,6 @@
                     </CheckBox.Template>
                 </CheckBox>
             </Grid>
-        </Grid>-->
+        </Grid>
     </Border>
 </UserControl>

+ 8 - 39
src/PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml.cs

@@ -3,55 +3,24 @@ using System.Windows.Controls;
 using System.Windows.Input;
 using Microsoft.Win32;
 using PixiEditor.Models.IO;
+using PixiEditor.ViewModels.SubViewModels.Document;
 
 namespace PixiEditor.Views.UserControls.Layers;
 
-/// <summary>
-/// Interaction logic for ReferenceLayer.xaml
-/// </summary>
+#nullable enable
 internal partial class ReferenceLayer : UserControl
 {
-    /*public Layer Layer
+    public static readonly DependencyProperty DocumentProperty =
+        DependencyProperty.Register(nameof(Document), typeof(DocumentViewModel), typeof(ReferenceLayer), new(null));
+
+    public DocumentViewModel? Document
     {
-        get { return (Layer)GetValue(ReferenceLayerProperty); }
-        set { SetValue(ReferenceLayerProperty, value); }
+        get => (DocumentViewModel?)GetValue(DocumentProperty);
+        set => SetValue(DocumentProperty, value);
     }
 
-
-    public static readonly DependencyProperty ReferenceLayerProperty =
-        DependencyProperty.Register(nameof(Layer), typeof(Layer), typeof(ReferenceLayer), new PropertyMetadata(default(Layer)));*/
-
-
     public ReferenceLayer()
     {
         InitializeComponent();
     }
-
-    private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
-    {
-        /*string path = OpenFilePicker();
-        if (path != null)
-        {
-            var bitmap = Importer.ImportSurface(path);
-            Layer = new Layer("_Reference Layer", bitmap, bitmap.Width, bitmap.Height);
-        }*/
-    }
-
-    private string OpenFilePicker()
-    {
-        var imagesFilter = new FileTypeDialogDataSet(FileTypeDialogDataSet.SetKind.Images).GetFormattedTypes();
-        OpenFileDialog dialog = new OpenFileDialog
-        {
-            Title = "Reference layer path",
-            CheckPathExists = true,
-            Filter = imagesFilter
-        };
-
-        return (bool)dialog.ShowDialog() ? dialog.FileName : null;
-    }
-
-    private void TrashButton_Click(object sender, RoutedEventArgs e)
-    {
-        //Layer = null;
-    }
 }

+ 5 - 4
src/PixiEditor/Views/UserControls/Viewport.xaml

@@ -149,15 +149,16 @@
                 <Grid>
                     <Canvas>
                         <Image
-                            Width="{Binding Document.ReferenceBitmap.Width}"
-                            Height="{Binding Document.ReferenceBitmap.Height}"
-                            Source="{Binding Document.ReferenceBitmap, Mode=OneWay}"
+                            Width="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Width}"
+                            Height="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Height}"
+                            Source="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, Mode=OneWay}"
+                            Visibility="{Binding Document.ReferenceLayerViewModel.IsVisibleBindable, Converter={converters:BoolToVisibilityConverter}}"
                             SizeChanged="OnReferenceImageSizeChanged"
                             RenderOptions.BitmapScalingMode="{Binding ReferenceLayerScale, Converter={converters:ScaleToBitmapScalingModeConverter}}">
                             <Image.RenderTransform>
                                 <TransformGroup>
                                     <MatrixTransform
-                                        Matrix="{Binding Document.ReferenceTransformMatrix}" />
+                                        Matrix="{Binding Document.ReferenceLayerViewModel.ReferenceTransformMatrix}" />
                                 </TransformGroup>
                             </Image.RenderTransform>
                         </Image>

+ 2 - 2
src/PixiEditor/Views/UserControls/Viewport.xaml.cs

@@ -274,8 +274,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
     }
 
     public double ReferenceLayerScale =>
-        ZoomboxScale * ((Document?.ReferenceBitmap != null)
-            ? (Document.ReferenceShape.RectSize.X / (double)Document.ReferenceBitmap.Width)
+        ZoomboxScale * ((Document?.ReferenceLayerViewModel.ReferenceBitmap != null && Document?.ReferenceLayerViewModel.ReferenceShapeBindable != null)
+            ? (Document.ReferenceLayerViewModel.ReferenceShapeBindable.RectSize.X / (double)Document.ReferenceLayerViewModel.ReferenceBitmap.PixelWidth)
             : 1);
 
     public PixiEditor.Zoombox.Zoombox Zoombox => zoombox;