Bläddra i källkod

We can see a viewport now

Equbuxu 3 år sedan
förälder
incheckning
abf7b7e0bf
37 ändrade filer med 598 tillägg och 79 borttagningar
  1. 19 0
      src/PixiEditor/Helpers/Converters/BlendModeToStringConverter.cs
  2. 18 0
      src/PixiEditor/Helpers/Converters/BoolToVisibilityConverter.cs
  3. 22 0
      src/PixiEditor/Helpers/Converters/ScaleToBitmapScalingModeConverter.cs
  4. 2 3
      src/PixiEditor/Helpers/UI/DocumentsTemplateSelector.cs
  5. 27 27
      src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs
  6. 1 1
      src/PixiEditor/Models/DocumentModels/DocumentHelpers.cs
  7. 1 1
      src/PixiEditor/Models/DocumentModels/DocumentState.cs
  8. 1 1
      src/PixiEditor/Models/DocumentModels/DocumentStructureHelper.cs
  9. 1 1
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  10. 1 1
      src/PixiEditor/Models/Rendering/AffectedChunkGatherer.cs
  11. 1 1
      src/PixiEditor/Models/Rendering/RenderInfos/CanvasPreviewDirty_RenderInfo.cs
  12. 1 1
      src/PixiEditor/Models/Rendering/RenderInfos/DirtyRect_RenderInfo.cs
  13. 1 1
      src/PixiEditor/Models/Rendering/RenderInfos/IRenderInfo.cs
  14. 1 1
      src/PixiEditor/Models/Rendering/RenderInfos/MaskPreviewDirty_RenderInfo.cs
  15. 1 1
      src/PixiEditor/Models/Rendering/RenderInfos/PreviewDirty_RenderInfo.cs
  16. 1 1
      src/PixiEditor/Models/Rendering/WriteableBitmapUpdater.cs
  17. 5 0
      src/PixiEditor/PixiEditor.csproj
  18. 1 0
      src/PixiEditor/ViewModels/Prototype/DocumentTransformViewModel.cs
  19. 1 1
      src/PixiEditor/ViewModels/Prototype/FolderViewModel.cs
  20. 1 1
      src/PixiEditor/ViewModels/Prototype/LayerViewModel.cs
  21. 1 1
      src/PixiEditor/ViewModels/Prototype/StructureMemberViewModel.cs
  22. 77 22
      src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs
  23. 1 2
      src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  24. 7 4
      src/PixiEditor/Views/MainWindow.xaml
  25. 1 1
      src/PixiEditor/Views/UserControls/SelectionOverlay.cs
  26. 1 0
      src/PixiEditor/Views/UserControls/SymmetryOverlay/SymmetryAxisDragInfo.cs
  27. 1 1
      src/PixiEditor/Views/UserControls/SymmetryOverlay/SymmetryOverlay.cs
  28. 1 1
      src/PixiEditor/Views/UserControls/TransformOverlay/Anchor.cs
  29. 1 0
      src/PixiEditor/Views/UserControls/TransformOverlay/TransformCornerFreedom.cs
  30. 1 0
      src/PixiEditor/Views/UserControls/TransformOverlay/TransformHelper.cs
  31. 9 3
      src/PixiEditor/Views/UserControls/TransformOverlay/TransformOverlay.cs
  32. 1 0
      src/PixiEditor/Views/UserControls/TransformOverlay/TransformSideFreedom.cs
  33. 1 0
      src/PixiEditor/Views/UserControls/TransformOverlay/TransformState.cs
  34. 1 0
      src/PixiEditor/Views/UserControls/TransformOverlay/TransformUpdateHelper.cs
  35. 130 0
      src/PixiEditor/Views/UserControls/Viewport.xaml
  36. 256 0
      src/PixiEditor/Views/UserControls/Viewport.xaml.cs
  37. 1 1
      src/PixiEditorPrototype/ViewModels/StructureMemberViewModel.cs

+ 19 - 0
src/PixiEditor/Helpers/Converters/BlendModeToStringConverter.cs

@@ -0,0 +1,19 @@
+using System.Globalization;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Helpers.Extensions;
+
+namespace PixiEditor.Helpers.Converters;
+internal class BlendModeToStringConverter : SingleInstanceConverter<BlendModeToStringConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is not BlendMode mode)
+            return "<null>";
+        return mode.EnglishName();
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 18 - 0
src/PixiEditor/Helpers/Converters/BoolToVisibilityConverter.cs

@@ -0,0 +1,18 @@
+using System.Globalization;
+using System.Windows;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class BoolToVisibilityConverter : SingleInstanceConverter<BoolToVisibilityConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        bool boolean = (bool)value;
+        return boolean ? Visibility.Visible : Visibility.Collapsed;
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 22 - 0
src/PixiEditor/Helpers/Converters/ScaleToBitmapScalingModeConverter.cs

@@ -0,0 +1,22 @@
+using System.Globalization;
+using System.Windows;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class ScaleToBitmapScalingModeConverter : SingleInstanceConverter<ScaleToBitmapScalingModeConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is not double scale)
+            return DependencyProperty.UnsetValue;
+        if (scale < 1)
+            return BitmapScalingMode.HighQuality;
+        return BitmapScalingMode.NearestNeighbor;
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 2 - 3
src/PixiEditor/Helpers/UI/DocumentsTemplateSelector.cs

@@ -1,7 +1,6 @@
 using System.Windows;
 using System.Windows.Controls;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.DataHolders.Document;
+using PixiEditor.ViewModels.SubViewModels.Document;
 
 namespace PixiEditor.Helpers.UI;
 
@@ -16,7 +15,7 @@ internal class DocumentsTemplateSelector : DataTemplateSelector
 
     public override DataTemplate SelectTemplate(object item, DependencyObject container)
     {
-        if (item is Document)
+        if (item is DocumentViewModel)
         {
             return DocumentsViewTemplate;
         }

+ 27 - 27
src/PixiEditor/Models/DocumentModels/ActionAccumulator.cs

@@ -10,7 +10,7 @@ using PixiEditor.ViewModels.Prototype;
 using PixiEditor.ViewModels.SubViewModels.Document;
 
 namespace PixiEditor.Models.DocumentModels;
-
+#nullable enable
 internal class ActionAccumulator
 {
     private bool executing = false;
@@ -153,35 +153,35 @@ internal class ActionAccumulator
             switch (renderInfo)
             {
                 case DirtyRect_RenderInfo info:
-                {
-                    var bitmap = document.Bitmaps[info.Resolution];
-                    RectI finalRect = new RectI(VecI.Zero, new(bitmap.PixelWidth, bitmap.PixelHeight));
-
-                    RectI dirtyRect = new RectI(info.Pos, info.Size).Intersect(finalRect);
-                    bitmap.AddDirtyRect(new(dirtyRect.Left, dirtyRect.Top, dirtyRect.Width, dirtyRect.Height));
-                }
-                break;
+                    {
+                        var bitmap = document.Bitmaps[info.Resolution];
+                        RectI finalRect = new RectI(VecI.Zero, new(bitmap.PixelWidth, bitmap.PixelHeight));
+
+                        RectI dirtyRect = new RectI(info.Pos, info.Size).Intersect(finalRect);
+                        bitmap.AddDirtyRect(new(dirtyRect.Left, dirtyRect.Top, dirtyRect.Width, dirtyRect.Height));
+                    }
+                    break;
                 case PreviewDirty_RenderInfo info:
-                {
-                    var bitmap = helpers.StructureHelper.Find(info.GuidValue)?.PreviewBitmap;
-                    if (bitmap is null)
-                        continue;
-                    bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
-                }
-                break;
+                    {
+                        var bitmap = helpers.StructureHelper.Find(info.GuidValue)?.PreviewBitmap;
+                        if (bitmap is null)
+                            continue;
+                        bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
+                    }
+                    break;
                 case MaskPreviewDirty_RenderInfo info:
-                {
-                    var bitmap = helpers.StructureHelper.Find(info.GuidValue)?.MaskPreviewBitmap;
-                    if (bitmap is null)
-                        continue;
-                    bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
-                }
-                break;
+                    {
+                        var bitmap = helpers.StructureHelper.Find(info.GuidValue)?.MaskPreviewBitmap;
+                        if (bitmap is null)
+                            continue;
+                        bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
+                    }
+                    break;
                 case CanvasPreviewDirty_RenderInfo:
-                {
-                    document.PreviewBitmap.AddDirtyRect(new Int32Rect(0, 0, document.PreviewBitmap.PixelWidth, document.PreviewBitmap.PixelHeight));
-                }
-                break;
+                    {
+                        document.PreviewBitmap.AddDirtyRect(new Int32Rect(0, 0, document.PreviewBitmap.PixelWidth, document.PreviewBitmap.PixelHeight));
+                    }
+                    break;
             }
         }
     }

+ 1 - 1
src/PixiEditor/Models/DocumentModels/DocumentHelpers.cs

@@ -2,7 +2,7 @@
 using PixiEditor.ViewModels.SubViewModels.Document;
 
 namespace PixiEditor.Models.DocumentModels;
-
+#nullable enable
 internal class DocumentHelpers
 {
     public DocumentHelpers(DocumentViewModel doc)

+ 1 - 1
src/PixiEditor/Models/DocumentModels/DocumentState.cs

@@ -1,7 +1,7 @@
 using PixiEditor.Models.Position;
 
 namespace PixiEditor.Models.DocumentModels;
-
+#nullable enable
 internal class DocumentState
 {
     public Dictionary<Guid, ViewportInfo> Viewports { get; set; } = new();

+ 1 - 1
src/PixiEditor/Models/DocumentModels/DocumentStructureHelper.cs

@@ -4,7 +4,7 @@ using PixiEditor.ViewModels.Prototype;
 using PixiEditor.ViewModels.SubViewModels.Document;
 
 namespace PixiEditor.Models.DocumentModels;
-
+#nullable enable
 internal class DocumentStructureHelper
 {
     private DocumentViewModel doc;

+ 1 - 1
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -13,7 +13,7 @@ using PixiEditor.ViewModels.SubViewModels.Document;
 using SkiaSharp;
 
 namespace PixiEditor.Models.DocumentModels;
-
+#nullable enable
 internal class DocumentUpdater
 {
     private DocumentViewModel doc;

+ 1 - 1
src/PixiEditor/Models/Rendering/AffectedChunkGatherer.cs

@@ -9,7 +9,7 @@ using PixiEditor.ChangeableDocument.ChangeInfos.Root;
 using PixiEditor.ChangeableDocument.ChangeInfos.Structure;
 
 namespace PixiEditor.Models.Rendering;
-
+#nullable enable
 internal class AffectedChunkGatherer
 {
     private readonly DocumentChangeTracker tracker;

+ 1 - 1
src/PixiEditor/Models/Rendering/RenderInfos/CanvasPreviewDirty_RenderInfo.cs

@@ -1,3 +1,3 @@
 namespace PixiEditor.Models.Rendering.RenderInfos;
-
+#nullable enable
 internal record CanvasPreviewDirty_RenderInfo : IRenderInfo;

+ 1 - 1
src/PixiEditor/Models/Rendering/RenderInfos/DirtyRect_RenderInfo.cs

@@ -1,5 +1,5 @@
 using ChunkyImageLib.DataHolders;
 
 namespace PixiEditor.Models.Rendering.RenderInfos;
-
+#nullable enable
 public record class DirtyRect_RenderInfo(VecI Pos, VecI Size, ChunkResolution Resolution) : IRenderInfo;

+ 1 - 1
src/PixiEditor/Models/Rendering/RenderInfos/IRenderInfo.cs

@@ -1,5 +1,5 @@
 namespace PixiEditor.Models.Rendering.RenderInfos;
-
+#nullable enable
 public interface IRenderInfo
 {
 }

+ 1 - 1
src/PixiEditor/Models/Rendering/RenderInfos/MaskPreviewDirty_RenderInfo.cs

@@ -1,3 +1,3 @@
 namespace PixiEditor.Models.Rendering.RenderInfos;
-
+#nullable enable
 public record class MaskPreviewDirty_RenderInfo(Guid GuidValue) : IRenderInfo;

+ 1 - 1
src/PixiEditor/Models/Rendering/RenderInfos/PreviewDirty_RenderInfo.cs

@@ -1,3 +1,3 @@
 namespace PixiEditor.Models.Rendering.RenderInfos;
-
+#nullable enable
 public record class PreviewDirty_RenderInfo(Guid GuidValue) : IRenderInfo;

+ 1 - 1
src/PixiEditor/Models/Rendering/WriteableBitmapUpdater.cs

@@ -11,7 +11,7 @@ using PixiEditor.ViewModels.SubViewModels.Document;
 using SkiaSharp;
 
 namespace PixiEditor.Models.Rendering;
-
+#nullable enable
 internal class WriteableBitmapUpdater
 {
     private readonly DocumentViewModel doc;

+ 5 - 0
src/PixiEditor/PixiEditor.csproj

@@ -359,4 +359,9 @@
 			<LastGenOutput>Settings.Designer.cs</LastGenOutput>
 		</None>
 	</ItemGroup>
+	<ItemGroup>
+	  <Page Update="Views\UserControls\Viewport.xaml">
+	    <XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
+	  </Page>
+	</ItemGroup>
 </Project>

+ 1 - 0
src/PixiEditor/ViewModels/Prototype/DocumentTransformViewModel.cs

@@ -3,6 +3,7 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.Views.UserControls.TransformOverlay;
 
 namespace PixiEditor.ViewModels.Prototype;
+#nullable enable
 internal class DocumentTransformViewModel : INotifyPropertyChanged
 {
     private TransformState internalState;

+ 1 - 1
src/PixiEditor/ViewModels/Prototype/FolderViewModel.cs

@@ -3,7 +3,7 @@ using PixiEditor.Models.DocumentModels;
 using PixiEditor.ViewModels.SubViewModels.Document;
 
 namespace PixiEditor.ViewModels.Prototype;
-
+#nullable enable
 internal class FolderViewModel : StructureMemberViewModel
 {
     public ObservableCollection<StructureMemberViewModel> Children { get; } = new();

+ 1 - 1
src/PixiEditor/ViewModels/Prototype/LayerViewModel.cs

@@ -3,7 +3,7 @@ using PixiEditor.Models.DocumentModels;
 using PixiEditor.ViewModels.SubViewModels.Document;
 
 namespace PixiEditor.ViewModels.Prototype;
-
+#nullable enable
 internal class LayerViewModel : StructureMemberViewModel
 {
     bool lockTransparency;

+ 1 - 1
src/PixiEditor/ViewModels/Prototype/StructureMemberViewModel.cs

@@ -10,7 +10,7 @@ using PixiEditor.ViewModels.SubViewModels.Document;
 using SkiaSharp;
 
 namespace PixiEditor.ViewModels.Prototype;
-
+#nullable enable
 internal abstract class StructureMemberViewModel : INotifyPropertyChanged
 {
     public event PropertyChangedEventHandler? PropertyChanged;

+ 77 - 22
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -7,11 +7,13 @@ using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DocumentModels;
+using PixiEditor.Models.Position;
 using PixiEditor.ViewModels.Prototype;
 using SkiaSharp;
 
 namespace PixiEditor.ViewModels.SubViewModels.Document;
 
+#nullable enable
 internal class DocumentViewModel : NotifyableObject
 {
     public const string ConfirmationDialogTitle = "Unsaved changes";
@@ -26,14 +28,14 @@ internal class DocumentViewModel : NotifyableObject
             RaisePropertyChanged(nameof(Busy));
         }
     }
-    
+
     public FolderViewModel StructureRoot { get; }
 
     public int Width => size.X;
     public int Height => size.Y;
-    
+
     public StructureMemberViewModel? SelectedStructureMember => FindFirstSelectedMember();
-    
+
     public Dictionary<ChunkResolution, WriteableBitmap> Bitmaps { get; set; } = new()
     {
         [ChunkResolution.Full] = new WriteableBitmap(64, 64, 96, 96, PixelFormats.Pbgra32, null),
@@ -48,24 +50,24 @@ internal class DocumentViewModel : NotifyableObject
     public Dictionary<ChunkResolution, SKSurface> Surfaces { get; set; } = new();
 
     public VecI SizeBindable => size;
-    
+
     public StructureMemberViewModel? FindFirstSelectedMember() => Helpers.StructureHelper.FindFirstWhere(member => member.IsSelected);
 
     public int HorizontalSymmetryAxisYBindable => horizontalSymmetryAxisY;
     public int VerticalSymmetryAxisXBindable => verticalSymmetryAxisX;
-    
+
     public bool HorizontalSymmetryAxisEnabledBindable
     {
         get => horizontalSymmetryAxisEnabled;
         set => Helpers.ActionAccumulator.AddFinishedActions(new SymmetryAxisState_Action(SymmetryAxisDirection.Horizontal, value));
     }
-    
+
     public bool VerticalSymmetryAxisEnabledBindable
     {
         get => verticalSymmetryAxisEnabled;
         set => Helpers.ActionAccumulator.AddFinishedActions(new SymmetryAxisState_Action(SymmetryAxisDirection.Vertical, value));
     }
-    
+
     public IReadOnlyReferenceLayer? ReferenceLayer => Helpers.Tracker.Document.ReferenceLayer;
 
     public BitmapSource? ReferenceBitmap => ReferenceLayer?.Image.ToWriteableBitmap();
@@ -82,31 +84,74 @@ internal class DocumentViewModel : NotifyableObject
             return new Matrix(skiaMatrix.ScaleX, skiaMatrix.SkewY, skiaMatrix.SkewX, skiaMatrix.ScaleY, skiaMatrix.TransX, skiaMatrix.TransY);
         }
     }
-    
+
     public SKPath SelectionPathBindable => selectionPath;
+    public DocumentTransformViewModel TransformViewModel { get; }
 
     private DocumentHelpers Helpers { get; }
 
-    private readonly ViewModelMain owner;
+    private readonly DocumentManagerViewModel owner;
 
     private int verticalSymmetryAxisX;
-    
+
     private bool horizontalSymmetryAxisEnabled;
 
     private bool verticalSymmetryAxisEnabled;
-    
+
     private bool busy = false;
-    
+
     private VecI size = new VecI(64, 64);
 
     private int horizontalSymmetryAxisY;
-    
+
     private SKPath selectionPath = new SKPath();
-    
-    public DocumentViewModel(string name)
+
+    public DocumentViewModel(DocumentManagerViewModel owner, string name)
     {
+        this.owner = owner;
+        //Name = name;
+        TransformViewModel = new();
+        //TransformViewModel.TransformMoved += OnTransformUpdate;
+
+        Helpers = new DocumentHelpers(this);
+        StructureRoot = new FolderViewModel(this, Helpers, Helpers.Tracker.Document.StructureRoot.GuidValue);
+
+        /*UndoCommand = new RelayCommand(Undo);
+        RedoCommand = new RelayCommand(Redo);
+        ClearSelectionCommand = new RelayCommand(ClearSelection);
+        CreateNewLayerCommand = new RelayCommand(_ => Helpers.StructureHelper.CreateNewStructureMember(StructureMemberType.Layer));
+        CreateNewFolderCommand = new RelayCommand(_ => Helpers.StructureHelper.CreateNewStructureMember(StructureMemberType.Folder));
+        DeleteStructureMemberCommand = new RelayCommand(DeleteStructureMember);
+        ResizeCanvasCommand = new RelayCommand(ResizeCanvas);
+        ResizeImageCommand = new RelayCommand(ResizeImage);
+        CombineCommand = new RelayCommand(Combine);
+        ClearHistoryCommand = new RelayCommand(ClearHistory);
+        CreateMaskCommand = new RelayCommand(CreateMask);
+        DeleteMaskCommand = new RelayCommand(DeleteMask);
+        ToggleLockTransparencyCommand = new RelayCommand(ToggleLockTransparency);
+        PasteImageCommand = new RelayCommand(PasteImage);
+        CreateReferenceLayerCommand = new RelayCommand(CreateReferenceLayer);
+        ApplyTransformCommand = new RelayCommand(ApplyTransform);
+        DragSymmetryCommand = new RelayCommand(DragSymmetry);
+        EndDragSymmetryCommand = new RelayCommand(EndDragSymmetry);
+        ClipToMemberBelowCommand = new RelayCommand(ClipToMemberBelow);
+        ApplyMaskCommand = new RelayCommand(ApplyMask);
+        TransformSelectionPathCommand = new RelayCommand(TransformSelectionPath);
+        TransformSelectedAreaCommand = new RelayCommand(TransformSelectedArea);*/
+
+        foreach (var bitmap in Bitmaps)
+        {
+            var surface = SKSurface.Create(
+                new SKImageInfo(bitmap.Value.PixelWidth, bitmap.Value.PixelHeight, SKColorType.Bgra8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb()),
+                bitmap.Value.BackBuffer, bitmap.Value.BackBufferStride);
+            Surfaces[bitmap.Key] = surface;
+        }
+
+        var previewSize = StructureMemberViewModel.CalculatePreviewSize(SizeBindable);
+        PreviewBitmap = new WriteableBitmap(previewSize.X, previewSize.Y, 96, 96, PixelFormats.Pbgra32, null);
+        PreviewSurface = SKSurface.Create(new SKImageInfo(previewSize.X, previewSize.Y, SKColorType.Bgra8888), PreviewBitmap.BackBuffer, PreviewBitmap.BackBufferStride);
     }
-    
+
     public void SetSize(VecI size)
     {
         this.size = size;
@@ -116,33 +161,43 @@ internal class DocumentViewModel : NotifyableObject
     }
 
     #region Symmetry
-    
+
     public void SetVerticalSymmetryAxisEnabled(bool verticalSymmetryAxisEnabled)
     {
         this.verticalSymmetryAxisEnabled = verticalSymmetryAxisEnabled;
         RaisePropertyChanged(nameof(VerticalSymmetryAxisEnabledBindable));
     }
-    
+
     public void SetHorizontalSymmetryAxisEnabled(bool horizontalSymmetryAxisEnabled)
     {
         this.horizontalSymmetryAxisEnabled = horizontalSymmetryAxisEnabled;
         RaisePropertyChanged(nameof(HorizontalSymmetryAxisEnabledBindable));
     }
-    
+
     public void SetVerticalSymmetryAxisX(int verticalSymmetryAxisX)
     {
         this.verticalSymmetryAxisX = verticalSymmetryAxisX;
         RaisePropertyChanged(nameof(VerticalSymmetryAxisXBindable));
     }
-    
+
     public void SetHorizontalSymmetryAxisY(int horizontalSymmetryAxisY)
     {
         this.horizontalSymmetryAxisY = horizontalSymmetryAxisY;
         RaisePropertyChanged(nameof(HorizontalSymmetryAxisYBindable));
     }
-    
+
     #endregion
-    
+
+    public void AddOrUpdateViewport(ViewportInfo info)
+    {
+        //Helpers.ActionAccumulator.AddActions(new RefreshViewport_PassthroughAction(info));
+    }
+
+    public void RemoveViewport(Guid viewportGuid)
+    {
+        //Helpers.ActionAccumulator.AddActions(new RemoveViewport_PassthroughAction(viewportGuid));
+    }
+
     public void SetSelectionPath(SKPath selectionPath)
     {
         (var toDispose, this.selectionPath) = (this.selectionPath, selectionPath);

+ 1 - 2
src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -1,6 +1,5 @@
 using System.Windows.Input;
 using Newtonsoft.Json.Linq;
-using PixiEditor.Models.Commands.Attributes;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
@@ -70,7 +69,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
 
     public void NewDocument(int width, int height, bool addBaseLayer = true)
     {
-        Owner.DocumentManagerSubViewModel.Documents.Add(new DocumentViewModel("Unnamed"));
+        Owner.DocumentManagerSubViewModel.Documents.Add(new DocumentViewModel(Owner.DocumentManagerSubViewModel, "Unnamed"));
         Owner.DocumentManagerSubViewModel.ActiveDocument = Owner.DocumentManagerSubViewModel.Documents[^1];
         /*
         if (addBaseLayer)

+ 7 - 4
src/PixiEditor/Views/MainWindow.xaml

@@ -481,8 +481,9 @@
             </StackPanel>
             <Grid Grid.Column="1" Grid.Row="2" Background="#303030" >
                 <Grid AllowDrop="True" Drop="MainWindow_Drop">
-                    <DockingManager ActiveContent="{Binding DocumentManagerSubViewModel.ActiveWindow, Mode=TwoWay}"
-                                           DocumentsSource="{Binding DocumentManagerSubViewModel.Documents}">
+                    <DockingManager 
+                        ActiveContent="{Binding DocumentManagerSubViewModel.ActiveWindow, Mode=TwoWay}"
+                        DocumentsSource="{Binding DocumentManagerSubViewModel.Documents}">
                         <DockingManager.Theme>
                             <avalonDockTheme:PixiEditorDockTheme />
                         </DockingManager.Theme>
@@ -501,7 +502,9 @@
                             <ui:DocumentsTemplateSelector>
                                 <ui:DocumentsTemplateSelector.DocumentsViewTemplate>
                                     <DataTemplate DataType="{x:Type doc:DocumentViewModel}">
-                                        <usercontrols:DrawingViewPort
+                                        <usercontrols:Viewport
+                                            Document="{Binding}"/>
+                                        <!--<usercontrols:DrawingViewPort
                                         CenterViewportTrigger="{Binding CenterViewportTrigger}"
                                         ZoomViewportTrigger="{Binding ZoomViewportTrigger}"
                                         GridLinesVisible="{Binding XamlAccesibleViewModel.ViewportSubViewModel.GridLinesEnabled}"
@@ -584,7 +587,7 @@
                                                     </ContextMenu.Template>
                                                 </ContextMenu>
                                             </usercontrols:DrawingViewPort.ContextMenu>
-                                        </usercontrols:DrawingViewPort>
+                                            </usercontrols:DrawingViewPort> -->
                                     </DataTemplate>
                                 </ui:DocumentsTemplateSelector.DocumentsViewTemplate>
                             </ui:DocumentsTemplateSelector>

+ 1 - 1
src/PixiEditor/Views/UserControls/SelectionOverlay.cs

@@ -5,7 +5,7 @@ using System.Windows.Media.Animation;
 using SkiaSharp;
 
 namespace PixiEditor.Views.UserControls;
-
+#nullable enable
 internal class SelectionOverlay : Control
 {
     public static readonly DependencyProperty PathProperty =

+ 1 - 0
src/PixiEditor/Views/UserControls/SymmetryOverlay/SymmetryAxisDragInfo.cs

@@ -1,4 +1,5 @@
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.Views.UserControls.SymmetryOverlay;
+#nullable enable
 internal record class SymmetryAxisDragInfo(SymmetryAxisDirection Direction, int NewPosition);

+ 1 - 1
src/PixiEditor/Views/UserControls/SymmetryOverlay/SymmetryOverlay.cs

@@ -6,7 +6,7 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Enums;
 
 namespace PixiEditor.Views.UserControls.SymmetryOverlay;
-
+#nullable enable
 internal class SymmetryOverlay : Control
 {
     public static readonly DependencyProperty HorizontalAxisYProperty =

+ 1 - 1
src/PixiEditor/Views/UserControls/TransformOverlay/Anchor.cs

@@ -1,5 +1,5 @@
 namespace PixiEditor.Views.UserControls.TransformOverlay;
-
+#nullable enable
 internal enum Anchor
 {
     TopLeft, TopRight, BottomLeft, BottomRight,

+ 1 - 0
src/PixiEditor/Views/UserControls/TransformOverlay/TransformCornerFreedom.cs

@@ -1,4 +1,5 @@
 namespace PixiEditor.Views.UserControls.TransformOverlay;
+#nullable enable
 internal enum TransformCornerFreedom
 {
     Locked,

+ 1 - 0
src/PixiEditor/Views/UserControls/TransformOverlay/TransformHelper.cs

@@ -1,5 +1,6 @@
 using System.Windows;
 using ChunkyImageLib.DataHolders;
+#nullable enable
 
 namespace PixiEditor.Views.UserControls.TransformOverlay;
 internal static class TransformHelper

+ 9 - 3
src/PixiEditor/Views/UserControls/TransformOverlay/TransformOverlay.cs

@@ -5,7 +5,7 @@ using System.Windows.Media;
 using ChunkyImageLib.DataHolders;
 
 namespace PixiEditor.Views.UserControls.TransformOverlay;
-
+#nullable enable
 internal class TransformOverlay : Control
 {
     public static readonly DependencyProperty RequestedCornersProperty =
@@ -219,7 +219,10 @@ internal class TransformOverlay : Control
 
             Corners = new ShapeCorners()
             {
-                BottomLeft = cornersOnStartMove.BottomLeft + delta, BottomRight = cornersOnStartMove.BottomRight + delta, TopLeft = cornersOnStartMove.TopLeft + delta, TopRight = cornersOnStartMove.TopRight + delta,
+                BottomLeft = cornersOnStartMove.BottomLeft + delta,
+                BottomRight = cornersOnStartMove.BottomRight + delta,
+                TopLeft = cornersOnStartMove.TopLeft + delta,
+                TopRight = cornersOnStartMove.TopRight + delta,
             };
 
             InternalState = InternalState with { Origin = originOnStartMove + delta };
@@ -305,7 +308,10 @@ internal class TransformOverlay : Control
         overlay.Corners = (ShapeCorners)args.NewValue;
         overlay.InternalState = new()
         {
-            ProportionalAngle1 = (overlay.Corners.BottomRight - overlay.Corners.TopLeft).Angle, ProportionalAngle2 = (overlay.Corners.TopRight - overlay.Corners.BottomLeft).Angle, OriginWasManuallyDragged = false, Origin = TransformHelper.OriginFromCorners(overlay.Corners),
+            ProportionalAngle1 = (overlay.Corners.BottomRight - overlay.Corners.TopLeft).Angle,
+            ProportionalAngle2 = (overlay.Corners.TopRight - overlay.Corners.BottomLeft).Angle,
+            OriginWasManuallyDragged = false,
+            Origin = TransformHelper.OriginFromCorners(overlay.Corners),
         };
         overlay.isResettingRequestedCorners = true;
         overlay.RequestedCorners = new ShapeCorners();

+ 1 - 0
src/PixiEditor/Views/UserControls/TransformOverlay/TransformSideFreedom.cs

@@ -1,4 +1,5 @@
 namespace PixiEditor.Views.UserControls.TransformOverlay;
+#nullable enable
 internal enum TransformSideFreedom
 {
     Locked,

+ 1 - 0
src/PixiEditor/Views/UserControls/TransformOverlay/TransformState.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib.DataHolders;
 
+#nullable enable
 namespace PixiEditor.Views.UserControls.TransformOverlay;
 internal struct TransformState
 {

+ 1 - 0
src/PixiEditor/Views/UserControls/TransformOverlay/TransformUpdateHelper.cs

@@ -1,6 +1,7 @@
 using ChunkyImageLib.DataHolders;
 
 namespace PixiEditor.Views.UserControls.TransformOverlay;
+#nullable enable
 internal static class TransformUpdateHelper
 {
     public static ShapeCorners? UpdateShapeFromCorner

+ 130 - 0
src/PixiEditor/Views/UserControls/Viewport.xaml

@@ -0,0 +1,130 @@
+<UserControl
+    x:Class="PixiEditor.Views.UserControls.Viewport"
+    x:ClassModifier="internal"
+    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"
+    xmlns:zoombox="clr-namespace:PixiEditor.Zoombox;assembly=PixiEditor.Zoombox"
+    xmlns:to="clr-namespace:PixiEditor.Views.UserControls.TransformOverlay"
+    xmlns:uc="clr-namespace:PixiEditor.Views.UserControls"
+    xmlns:sym="clr-namespace:PixiEditor.Views.UserControls.SymmetryOverlay"
+    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
+    xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+    mc:Ignorable="d"
+    x:Name="vpUc"
+    d:DesignHeight="450"
+    d:DesignWidth="800">
+    <Grid>
+        <zoombox:Zoombox
+            x:Name="zoombox"
+            UseTouchGestures="True"
+            Scale="{Binding ZoomboxScale, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWayToSource}"
+            Center="{Binding Center, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWayToSource}"
+            Angle="{Binding Angle, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWayToSource}"
+            RealDimensions="{Binding RealDimensions, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWayToSource}"
+            Dimensions="{Binding Dimensions, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=OneWayToSource}"
+            ZoomMode="{Binding ZoomMode, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}, Mode=TwoWay}"
+            FlipX="{Binding FlipX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}}"
+            FlipY="{Binding FlipY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}}">
+            <Border
+                BorderThickness="1"
+                Background="White"
+                BorderBrush="Black"
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center"
+                DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:Viewport}}">
+                <Grid>
+                    <Canvas>
+                        <Image
+                            Width="{Binding Document.ReferenceBitmap.Width}"
+                            Height="{Binding Document.ReferenceBitmap.Height}"
+                            Source="{Binding Document.ReferenceBitmap, Mode=OneWay}"
+                            SizeChanged="OnReferenceImageSizeChanged"
+                            RenderOptions.BitmapScalingMode="{Binding ReferenceLayerScale, Converter={converters:ScaleToBitmapScalingModeConverter}}">
+                            <Image.RenderTransform>
+                                <TransformGroup>
+                                    <MatrixTransform
+                                        Matrix="{Binding Document.ReferenceTransformMatrix}" />
+                                </TransformGroup>
+                            </Image.RenderTransform>
+                        </Image>
+                    </Canvas>
+                    <Image
+                        Focusable="True"
+                        Width="{Binding Document.Width}"
+                        Height="{Binding Document.Height}"
+                        Source="{Binding TargetBitmap}"
+                        RenderOptions.BitmapScalingMode="{Binding Zoombox.Scale, Converter={converters:ScaleToBitmapScalingModeConverter}}">
+                        <i:Interaction.Triggers>
+                            <i:EventTrigger
+                                EventName="MouseDown">
+                                <i:InvokeCommandAction
+                                    Command="{Binding MouseDownCommand}"
+                                    PassEventArgsToCommand="True" />
+                            </i:EventTrigger>
+                            <i:EventTrigger
+                                EventName="MouseMove">
+                                <i:InvokeCommandAction
+                                    Command="{Binding MouseMoveCommand}"
+                                    PassEventArgsToCommand="True" />
+                            </i:EventTrigger>
+                            <i:EventTrigger
+                                EventName="MouseUp">
+                                <i:InvokeCommandAction
+                                    Command="{Binding MouseUpCommand}"
+                                    PassEventArgsToCommand="True" />
+                            </i:EventTrigger>
+                        </i:Interaction.Triggers>
+                    </Image>
+                    <sym:SymmetryOverlay
+                        ZoomboxScale="{Binding Zoombox.Scale}"
+                        HorizontalAxisVisible="{Binding Document.HorizontalSymmetryAxisEnabledBindable}"
+                        VerticalAxisVisible="{Binding Document.VerticalSymmetryAxisEnabledBindable}"
+                        HorizontalAxisY="{Binding Document.HorizontalSymmetryAxisYBindable, Mode=OneWay}"
+                        VerticalAxisX="{Binding Document.VerticalSymmetryAxisXBindable, Mode=OneWay}"
+                        DragCommand="{Binding Document.DragSymmetryCommand}"
+                        DragEndCommand="{Binding Document.EndDragSymmetryCommand}" />
+                    <uc:SelectionOverlay
+                        Path="{Binding Document.SelectionPathBindable}"
+                        ZoomboxScale="{Binding Zoombox.Scale}" />
+                    <to:TransformOverlay
+                        HorizontalAlignment="Stretch"
+                        VerticalAlignment="Stretch"
+                        Visibility="{Binding Document.TransformViewModel.TransformActive, Converter={converters:BoolToVisibilityConverter}}"
+                        Corners="{Binding Document.TransformViewModel.Corners, Mode=TwoWay}"
+                        RequestedCorners="{Binding Document.TransformViewModel.RequestedCorners, Mode=TwoWay}"
+                        CornerFreedom="{Binding Document.TransformViewModel.CornerFreedom}"
+                        SideFreedom="{Binding Document.TransformViewModel.SideFreedom}"
+                        InternalState="{Binding Document.TransformViewModel.InternalState, Mode=TwoWay}"
+                        ZoomboxScale="{Binding Zoombox.Scale}" />
+                </Grid>
+            </Border>
+        </zoombox:Zoombox>
+        <Grid
+            Focusable="False">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition
+                    Width="1*" />
+                <ColumnDefinition
+                    Width="2*" />
+                <ColumnDefinition
+                    Width="1*" />
+            </Grid.ColumnDefinitions>
+            <Grid.RowDefinitions>
+                <RowDefinition
+                    Height="1*" />
+                <RowDefinition
+                    Height="2*" />
+                <RowDefinition
+                    Height="1*" />
+            </Grid.RowDefinitions>
+            <Border
+                BorderBrush="Red"
+                Grid.Row="1"
+                Grid.Column="1"
+                BorderThickness="1" />
+        </Grid>
+    </Grid>
+</UserControl>

+ 256 - 0
src/PixiEditor/Views/UserControls/Viewport.xaml.cs

@@ -0,0 +1,256 @@
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Media.Imaging;
+using ChunkyImageLib.DataHolders;
+using PixiEditor.Models.Position;
+using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.Zoombox;
+
+namespace PixiEditor.Views.UserControls;
+
+#nullable enable
+internal partial class Viewport : UserControl, INotifyPropertyChanged
+{
+    public event PropertyChangedEventHandler? PropertyChanged;
+
+    public static readonly DependencyProperty FlipXProperty =
+        DependencyProperty.Register(nameof(FlipX), typeof(bool), typeof(Viewport), new(false));
+
+    public static readonly DependencyProperty FlipYProperty =
+        DependencyProperty.Register(nameof(FlipY), typeof(bool), typeof(Viewport), new(false));
+
+    public static readonly DependencyProperty ZoomModeProperty =
+        DependencyProperty.Register(nameof(ZoomMode), typeof(ZoomboxMode), typeof(Viewport), new(ZoomboxMode.Normal));
+
+    public static readonly DependencyProperty DocumentProperty =
+        DependencyProperty.Register(nameof(Document), typeof(DocumentViewModel), typeof(Viewport), new(null, OnDocumentChange));
+
+    public static readonly DependencyProperty MouseDownCommandProperty =
+        DependencyProperty.Register(nameof(MouseDownCommand), typeof(ICommand), typeof(Viewport), new(null));
+
+    public static readonly DependencyProperty MouseMoveCommandProperty =
+        DependencyProperty.Register(nameof(MouseMoveCommand), typeof(ICommand), typeof(Viewport), new(null));
+
+    public static readonly DependencyProperty MouseUpCommandProperty =
+        DependencyProperty.Register(nameof(MouseUpCommand), typeof(ICommand), typeof(Viewport), new(null));
+
+    private static readonly DependencyProperty BitmapsProperty =
+        DependencyProperty.Register(nameof(Bitmaps), typeof(Dictionary<ChunkResolution, WriteableBitmap>), typeof(Viewport), new(null, OnBitmapsChange));
+
+    public static readonly DependencyProperty DelayedProperty = DependencyProperty.Register(
+        nameof(Delayed), typeof(bool), typeof(Viewport), new PropertyMetadata(false));
+
+    public bool Delayed
+    {
+        get => (bool)GetValue(DelayedProperty);
+        set => SetValue(DelayedProperty, value);
+    }
+
+    public Dictionary<ChunkResolution, WriteableBitmap>? Bitmaps
+    {
+        get => (Dictionary<ChunkResolution, WriteableBitmap>?)GetValue(BitmapsProperty);
+        set => SetValue(BitmapsProperty, value);
+    }
+
+    public ICommand? MouseDownCommand
+    {
+        get => (ICommand?)GetValue(MouseDownCommandProperty);
+        set => SetValue(MouseDownCommandProperty, value);
+    }
+
+    public ICommand? MouseMoveCommand
+    {
+        get => (ICommand?)GetValue(MouseMoveCommandProperty);
+        set => SetValue(MouseMoveCommandProperty, value);
+    }
+
+    public ICommand? MouseUpCommand
+    {
+        get => (ICommand?)GetValue(MouseUpCommandProperty);
+        set => SetValue(MouseUpCommandProperty, value);
+    }
+
+
+    public DocumentViewModel? Document
+    {
+        get => (DocumentViewModel)GetValue(DocumentProperty);
+        set => SetValue(DocumentProperty, value);
+    }
+
+    public ZoomboxMode ZoomMode
+    {
+        get => (ZoomboxMode)GetValue(ZoomModeProperty);
+        set => SetValue(ZoomModeProperty, value);
+    }
+
+    public double ZoomboxScale
+    {
+        get => zoombox.Scale;
+        // ReSharper disable once ValueParameterNotUsed
+        set
+        {
+            PropertyChanged?.Invoke(this, new(nameof(ReferenceLayerScale)));
+        }
+    }
+
+    public bool FlipX
+    {
+        get => (bool)GetValue(FlipXProperty);
+        set => SetValue(FlipXProperty, value);
+    }
+
+    public bool FlipY
+    {
+        get => (bool)GetValue(FlipYProperty);
+        set => SetValue(FlipYProperty, value);
+    }
+
+    private double angle = 0;
+
+    public double Angle
+    {
+        get => angle;
+        set
+        {
+            angle = value;
+            PropertyChanged?.Invoke(this, new(nameof(Angle)));
+            Document?.AddOrUpdateViewport(GetLocation());
+        }
+    }
+
+    private VecD center = new(32, 32);
+
+    public VecD Center
+    {
+        get => center;
+        set
+        {
+            center = value;
+            PropertyChanged?.Invoke(this, new(nameof(Center)));
+            Document?.AddOrUpdateViewport(GetLocation());
+        }
+    }
+
+    private VecD realDimensions = new(double.MaxValue, double.MaxValue);
+
+    public VecD RealDimensions
+    {
+        get => realDimensions;
+        set
+        {
+            var oldRes = CalculateResolution();
+            realDimensions = value;
+            var newRes = CalculateResolution();
+
+            PropertyChanged?.Invoke(this, new(nameof(RealDimensions)));
+            Document?.AddOrUpdateViewport(GetLocation());
+
+            if (oldRes != newRes)
+                PropertyChanged?.Invoke(this, new(nameof(TargetBitmap)));
+        }
+    }
+
+    private VecD dimensions = new(64, 64);
+
+    public VecD Dimensions
+    {
+        get => dimensions;
+        set
+        {
+            var oldRes = CalculateResolution();
+            dimensions = value;
+            var newRes = CalculateResolution();
+
+            PropertyChanged?.Invoke(this, new(nameof(Dimensions)));
+            Document?.AddOrUpdateViewport(GetLocation());
+
+            if (oldRes != newRes)
+                PropertyChanged?.Invoke(this, new(nameof(TargetBitmap)));
+        }
+    }
+
+    public WriteableBitmap? TargetBitmap
+    {
+        get
+        {
+            return Document?.Bitmaps.TryGetValue(CalculateResolution(), out var value) == true ? value : null;
+        }
+    }
+
+    public double ReferenceLayerScale =>
+        ZoomboxScale * ((Document?.ReferenceBitmap != null)
+            ? (Document.ReferenceShape.RectSize.X / (double)Document.ReferenceBitmap.Width)
+            : 1);
+
+    public PixiEditor.Zoombox.Zoombox Zoombox => zoombox;
+
+    public Guid GuidValue { get; } = Guid.NewGuid();
+
+    public Viewport()
+    {
+        InitializeComponent();
+
+        Binding binding = new Binding { Source = this, Path = new PropertyPath("Document.Bitmaps") };
+        SetBinding(BitmapsProperty, binding);
+
+        Loaded += OnLoad;
+        Unloaded += OnUnload;
+    }
+
+    private Image? GetImage() => (Image?)((Grid?)((Border?)zoombox.AdditionalContent)?.Child)?.Children[1];
+
+    private void ForceRefreshFinalImage()
+    {
+        GetImage()?.InvalidateVisual();
+    }
+
+    private void OnUnload(object sender, RoutedEventArgs e)
+    {
+        Document?.RemoveViewport(GuidValue);
+    }
+
+    private void OnLoad(object sender, RoutedEventArgs e)
+    {
+        Document?.AddOrUpdateViewport(GetLocation());
+    }
+
+    private static void OnDocumentChange(DependencyObject viewportObj, DependencyPropertyChangedEventArgs args)
+    {
+        var oldDoc = (DocumentViewModel?)args.OldValue;
+        var newDoc = (DocumentViewModel?)args.NewValue;
+        var viewport = (Viewport)viewportObj;
+        oldDoc?.RemoveViewport(viewport.GuidValue);
+        newDoc?.AddOrUpdateViewport(viewport.GetLocation());
+    }
+
+    private static void OnBitmapsChange(DependencyObject viewportObj, DependencyPropertyChangedEventArgs args)
+    {
+        ((Viewport)viewportObj).PropertyChanged?.Invoke(viewportObj, new(nameof(TargetBitmap)));
+    }
+
+    private ChunkResolution CalculateResolution()
+    {
+        VecD densityVec = Dimensions.Divide(RealDimensions);
+        double density = Math.Min(densityVec.X, densityVec.Y);
+        if (density > 8.01)
+            return ChunkResolution.Eighth;
+        else if (density > 4.01)
+            return ChunkResolution.Quarter;
+        else if (density > 2.01)
+            return ChunkResolution.Half;
+        return ChunkResolution.Full;
+    }
+
+    private ViewportInfo GetLocation()
+    {
+        return new(Angle, Center, RealDimensions / 2, Dimensions / 2, CalculateResolution(), GuidValue, Delayed, ForceRefreshFinalImage);
+    }
+
+    private void OnReferenceImageSizeChanged(object? sender, SizeChangedEventArgs e)
+    {
+        PropertyChanged?.Invoke(this, new(nameof(ReferenceLayerScale)));
+    }
+}

+ 1 - 1
src/PixiEditorPrototype/ViewModels/StructureMemberViewModel.cs

@@ -8,7 +8,7 @@ using PixiEditorPrototype.Models;
 using SkiaSharp;
 
 namespace PixiEditorPrototype.ViewModels;
-
+#nullable enable
 internal abstract class StructureMemberViewModel : INotifyPropertyChanged
 {
     public event PropertyChangedEventHandler? PropertyChanged;