Browse Source

Rotate image

flabbet 2 years ago
parent
commit
cfb423332e

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs

@@ -19,7 +19,7 @@ internal abstract class ResizeBasedChangeBase : Change
         return true;
     }
     
-    protected void Resize(ChunkyImage img, Guid memberGuid, VecI size, VecI offset, Dictionary<Guid, CommittedChunkStorage> deletedChunksDict)
+    protected virtual void Resize(ChunkyImage img, Guid memberGuid, VecI size, VecI offset, Dictionary<Guid, CommittedChunkStorage> deletedChunksDict)
     {
         img.EnqueueResize(size);
         img.EnqueueClear();

+ 109 - 0
src/PixiEditor.ChangeableDocument/Changes/Root/RotateImage_Change.cs

@@ -0,0 +1,109 @@
+using ChunkyImageLib.Operations;
+using PixiEditor.ChangeableDocument.ChangeInfos.Root;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
+using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
+
+namespace PixiEditor.ChangeableDocument.Changes.Root;
+
+internal sealed class RotateImage_Change : ResizeBasedChangeBase
+{
+    private readonly RotationAngle rotation;
+
+    [GenerateMakeChangeAction]
+    public RotateImage_Change(RotationAngle rotation)
+    {
+        this.rotation = rotation;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        var changes = Rotate(target);
+        
+        ignoreInUndo = false;
+        return changes;
+    }
+
+    protected override void Resize(ChunkyImage img, Guid memberGuid, VecI size, VecI offset, Dictionary<Guid, CommittedChunkStorage> deletedChunksDict)
+    {
+        using Paint paint = new()
+        {
+            BlendMode = DrawingApi.Core.Surface.BlendMode.Src
+        };
+        
+        using Surface originalSurface = new(_originalSize);
+        img.DrawMostUpToDateRegionOn(
+            new RectI(VecI.Zero, _originalSize), 
+            ChunkResolution.Full,
+            originalSurface.DrawingSurface,
+            VecI.Zero);
+
+        using Surface flipped = new Surface(size);
+
+        float translationX = size.X;
+        float translationY = size.Y;
+        if (rotation == RotationAngle.D90)
+        {
+            translationY = 0;
+        }
+        else if (rotation == RotationAngle.D270)
+        {
+            translationX = 0;
+        }
+        
+        flipped.DrawingSurface.Canvas.Save();
+        flipped.DrawingSurface.Canvas.Translate(translationX, translationY);
+        flipped.DrawingSurface.Canvas.RotateRadians(RotationAngleToRadians(rotation), 0, 0);
+        flipped.DrawingSurface.Canvas.DrawSurface(originalSurface.DrawingSurface, 0, 0, paint);
+        flipped.DrawingSurface.Canvas.Restore();
+        
+        img.EnqueueResize(size);
+        img.EnqueueClear();
+        img.EnqueueDrawImage(VecI.Zero, flipped);
+
+        deletedChunksDict.Add(memberGuid, new CommittedChunkStorage(img, img.FindAffectedChunks()));
+        img.CommitChanges();
+    }
+
+    private float RotationAngleToRadians(RotationAngle rotationAngle)
+    {
+        return rotationAngle switch
+        {
+            RotationAngle.D90 => 90f * Matrix3X3.DegreesToRadians,
+            RotationAngle.D180 => 180f * Matrix3X3.DegreesToRadians,
+            RotationAngle.D270 => 270f * Matrix3X3.DegreesToRadians,
+            _ => throw new ArgumentOutOfRangeException(nameof(rotationAngle), rotationAngle, null)
+        };
+    }
+    
+    private OneOf<None, IChangeInfo, List<IChangeInfo>> Rotate(Document target)
+    {
+        int newWidth = rotation == RotationAngle.D180 ? target.Size.X : target.Size.Y;
+        int newHeight = rotation == RotationAngle.D180 ? target.Size.Y : target.Size.X;
+
+        VecI newSize = new VecI(newWidth, newHeight);
+
+        float normalizedSymmX = _originalVerAxisX / Math.Max(target.Size.X, 0.1f);
+        float normalizedSymmY = _originalHorAxisY / Math.Max(target.Size.Y, 0.1f);
+        
+        target.Size = newSize;
+        target.VerticalSymmetryAxisX = (int)(newSize.X * normalizedSymmX);
+        target.HorizontalSymmetryAxisY = (int)(newSize.Y * normalizedSymmY);
+        
+        target.ForEveryMember((member) =>
+        {
+            if (member is Layer layer)
+            {
+                Resize(layer.LayerImage, layer.GuidValue, newSize, VecI.Zero, deletedChunks);
+            }
+            if (member.Mask is null)
+                return;
+
+            Resize(member.Mask, member.GuidValue, newSize, VecI.Zero, deletedMaskChunks);
+        });
+        
+        return new Size_ChangeInfo(newSize, target.VerticalSymmetryAxisX, target.HorizontalSymmetryAxisY);
+    }
+}

+ 10 - 0
src/PixiEditor.ChangeableDocument/Enums/FlipType.cs

@@ -5,3 +5,13 @@ public enum FlipType
     Horizontal,
     Vertical
 }
+
+/// <summary>
+///     Rotation specified in degrees
+/// </summary>
+public enum RotationAngle
+{
+    D90,
+    D180,
+    D270
+}

+ 1 - 1
src/PixiEditor.DrawingApi.Core/Numerics/Matrix3X3.cs

@@ -12,7 +12,7 @@ namespace PixiEditor.DrawingApi.Core.Numerics;
 /// </remarks>
 public struct Matrix3X3 : IEquatable<Matrix3X3>
 {
-    internal const float DegreesToRadians = 0.017453292f;
+    public const float DegreesToRadians = 0.017453292f;
 
     public static readonly Matrix3X3 Identity = new() { ScaleX = 1f, ScaleY = 1f, Persp2 = 1f };
     

+ 8 - 0
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -300,6 +300,14 @@ internal class DocumentOperationsModule
         Internals.ActionAccumulator.AddFinishedActions(new FlipImage_Action(flipType, membersToFlip));
     }
 
+    public void RotateImage(RotationAngle rotation)
+    {
+        if (Internals.ChangeController.IsChangeActive)
+            return;
+        
+        Internals.ActionAccumulator.AddFinishedActions(new RotateImage_Action(rotation));
+    }
+
     public void CenterContent(IReadOnlyList<Guid> structureMembers)
     {
         if (Internals.ChangeController.IsChangeActive)

+ 28 - 0
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentManagerViewModel.cs

@@ -93,6 +93,34 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>
         
         ActiveDocument?.Operations.FlipImage(FlipType.Vertical, ActiveDocument.GetSelectedMembers());
     }
+    
+    [Command.Basic("PixiEditor.Document.Rotate90Deg", "Rotate Image 90 deg", "Rotate Image 90 deg", CanExecute = "PixiEditor.HasDocument")]
+    public void Rotate90Deg()
+    {
+        if (ActiveDocument?.SelectedStructureMember == null)
+            return;
+        
+        ActiveDocument?.Operations.RotateImage(RotationAngle.D90);
+    }
+    
+    [Command.Basic("PixiEditor.Document.Rotate180Deg", "Rotate Image 180 deg", "Rotate Image 180 deg", CanExecute = "PixiEditor.HasDocument")]
+    public void Rotate180Deg()
+    {
+        if (ActiveDocument?.SelectedStructureMember == null)
+            return;
+        
+        ActiveDocument?.Operations.RotateImage(RotationAngle.D180);
+    }
+    
+    [Command.Basic("PixiEditor.Document.Rotate270Deg", "Rotate Image 270 deg", "Rotate Image 270 deg", CanExecute = "PixiEditor.HasDocument")]
+    public void Rotate270Deg()
+    {
+        if (ActiveDocument?.SelectedStructureMember == null)
+            return;
+        
+        ActiveDocument?.Operations.RotateImage(RotationAngle.D270);
+    }
+
 
     [Command.Basic("PixiEditor.Document.ToggleVerticalSymmetryAxis", "Toggle vertical symmetry axis", "Toggle vertical symmetry axis", CanExecute = "PixiEditor.HasDocument", IconPath = "SymmetryVertical.png")]
     public void ToggleVerticalSymmetryAxis()

+ 5 - 0
src/PixiEditor/Views/MainWindow.xaml

@@ -262,6 +262,11 @@
                             <MenuItem Header="Flip Image _Vertically" cmds:Menu.Command="PixiEditor.Document.FlipImageVertical"/>
                             <MenuItem Header="Flip Selected Layers _Horizontally" cmds:Menu.Command="PixiEditor.Document.FlipLayersHorizontal"/>
                             <MenuItem Header="Flip Selected Layers _Vertically" cmds:Menu.Command="PixiEditor.Document.FlipLayersVertical"/>
+                            
+                            <Separator/>
+                            <MenuItem Header="Rotate Image 90&#186;" cmds:Menu.Command="PixiEditor.Document.Rotate90Deg"/>
+                            <MenuItem Header="Rotate Image 180&#186;" cmds:Menu.Command="PixiEditor.Document.Rotate180Deg"/>
+                            <MenuItem Header="Rotate Image 270&#186;" cmds:Menu.Command="PixiEditor.Document.Rotate270Deg"/>
                         </MenuItem>
                     </MenuItem>
                     <MenuItem