Browse Source

Crop resizing updates

Ruben 10 months ago
parent
commit
ae84d33570

+ 30 - 13
src/PicView.Avalonia/ViewModels/ImageCropperViewModel.cs

@@ -1,16 +1,43 @@
-using Avalonia;
+using System.Reactive;
+using Avalonia;
 using Avalonia.Media.Imaging;
+using PicView.Avalonia.Crop;
+using PicView.Avalonia.UI;
+using PicView.Core.Localization;
 using ReactiveUI;
 
 namespace PicView.Avalonia.ViewModels;
 
-public class ImageCropperViewModel(Bitmap bitmap) : ViewModelBase
+public class ImageCropperViewModel : ViewModelBase
 {
+    public ImageCropperViewModel(Bitmap bitmap)
+    {
+        Bitmap = bitmap;
+        CropImageCommand  = ReactiveCommand.CreateFromTask(async () =>
+        {
+        
+        });
+        CloseCropCommand  = ReactiveCommand.Create(() =>
+        {
+            if (UIHelper.GetMainView.DataContext is not MainViewModel vm)
+            {
+                return;
+            }
+            CropFunctions.CloseCropControl(vm);
+        });
+        Crop = TranslationHelper.Translation.CropPicture;
+        Close = TranslationHelper.Translation.Close;
+    }
+    
+    public ReactiveCommand<Unit, Unit>? CropImageCommand { get; }
+    
+    public ReactiveCommand<Unit, Unit>? CloseCropCommand { get; }
+    
     public Bitmap Bitmap
     {
         get;
         set => this.RaiseAndSetIfChanged(ref field, value);
-    } = bitmap;
+    }
 
     public double SelectionX
     {
@@ -46,16 +73,6 @@ public class ImageCropperViewModel(Bitmap bitmap) : ViewModelBase
         get;
         set => this.RaiseAndSetIfChanged(ref field, value);
     }
-    
-    public double BottomOverlayHeight
-    {
-        get => ImageHeight - (SelectionY + SelectionHeight);
-    }
-
-    public double RightOverlayWidth
-    {
-        get => ImageWidth - (SelectionX + SelectionWidth);
-    }
 
     // Call this method when the user completes the selection
     public CroppedBitmap GetCroppedBitmap()

+ 91 - 3
src/PicView.Avalonia/Views/UC/CropControl.axaml

@@ -9,22 +9,110 @@
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:viewModels="clr-namespace:PicView.Avalonia.ViewModels"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <UserControl.Resources>
+        <SolidColorBrush Color="{DynamicResource MainTextColor}" x:Key="Brush0" />
+    </UserControl.Resources>
+    <UserControl.Styles>
+        <Styles>
+            <Style Selector="Border.x">
+                <Setter Property="Background" Value="{DynamicResource SecondaryButtonBackgroundColor}" />
+                <Setter Property="BorderBrush" Value="#fff" />
+                <Setter Property="BorderThickness" Value="2" />
+                <Setter Property="Width" Value="20" />
+                <Setter Property="Height" Value="20" />
+                <Setter Property="ZIndex" Value="1" />
+                <Setter Property="CornerRadius" Value="2" />
+            </Style>
+        </Styles>
+    </UserControl.Styles>
+    <UserControl.ContextMenu>
+        <ContextMenu x:Name="CropContextMenu">
+            <MenuItem Command="{CompiledBinding CropImageCommand}" Header="{CompiledBinding Crop, Mode=OneWay}">
+                <MenuItem.Icon>
+                    <Path
+                        Data="{StaticResource CropGeometry}"
+                        Fill="{StaticResource Brush0}"
+                        Height="12"
+                        Stretch="Fill"
+                        Width="12" />
+                </MenuItem.Icon>
+            </MenuItem>
+            <MenuItem Command="{CompiledBinding CloseCropCommand}" Header="{CompiledBinding Close, Mode=OneWay}">
+                <MenuItem.Icon>
+                    <Path
+                        Data="{StaticResource CloseGeometry}"
+                        Fill="{StaticResource Brush0}"
+                        Height="12"
+                        Stretch="Fill"
+                        Width="12" />
+                </MenuItem.Icon>
+            </MenuItem>
+        </ContextMenu>
+    </UserControl.ContextMenu>
     <Panel>
         <!--  Image control to display the bitmap  -->
         <Image Source="{Binding Bitmap}" x:Name="ImageControl" />
 
 
 
-        <Canvas x:Name="RootCanvas">
+        <Canvas Background="Transparent" x:Name="RootCanvas">
             <!--  Main Rectangle  -->
             <Border
                 Background="Transparent"
-                BorderBrush="{DynamicResource MainBorderColor}"
-                BorderThickness="1"
+                BorderBrush="#fff"
+                BorderThickness="2"
                 Height="{CompiledBinding SelectionHeight}"
                 Width="{CompiledBinding SelectionWidth}"
                 x:Name="MainRectangle" />
 
+            <!--  Top Left  -->
+            <Border
+                Classes="x"
+                Cursor="TopLeftCorner"
+                x:Name="TopLeftButton" />
+
+            <!--  Top Right  -->
+            <Border
+                Classes="x"
+                Cursor="TopRightCorner"
+                x:Name="TopRightButton" />
+
+            <!--  Top Middle  -->
+            <Border
+                Classes="x"
+                Cursor="SizeNorthSouth"
+                x:Name="TopMiddleButton" />
+
+            <!--  Bottom Right  -->
+            <Border
+                Classes="x"
+                Cursor="BottomRightCorner"
+                x:Name="BottomRightButton" />
+
+            <!--  Bottom Middle  -->
+            <Border
+                Classes="x"
+                Cursor="SizeNorthSouth"
+                x:Name="BottomMiddleButton" />
+
+            <!--  Bottom Left  -->
+            <Border
+                Classes="x"
+                Cursor="BottomLeftCorner"
+                x:Name="BottomLeftButton" />
+
+            <!--  Left Middle  -->
+            <Border
+                Classes="x"
+                Cursor="SizeWestEast"
+                x:Name="LeftMiddleButton" />
+
+            <!--  Right Middle  -->
+            <Border
+                Classes="x"
+                Cursor="SizeWestEast"
+                x:Name="RightMiddleButton" />
+
             <!--  Surrounding rectangles  -->
             <Rectangle Fill="#80000000" x:Name="TopRectangle" />
             <Rectangle Fill="#80000000" x:Name="BottomRectangle" />

+ 317 - 8
src/PicView.Avalonia/Views/UC/CropControl.axaml.cs

@@ -1,6 +1,7 @@
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Input;
+using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 
 namespace PicView.Avalonia.Views.UC;
@@ -10,6 +11,8 @@ public partial class CropControl : UserControl
     private Point _dragStart;
     private bool _isDragging;
     private Rect _originalRect;
+    private Point _resizeStart;
+    private bool _isResizing;
 
     public CropControl()
     {
@@ -20,6 +23,33 @@ public partial class CropControl : UserControl
             MainRectangle.PointerPressed += OnPointerPressed;
             MainRectangle.PointerReleased += OnPointerReleased;
             MainRectangle.PointerMoved += OnPointerMoved;
+            
+            TopLeftButton.PointerPressed += OnResizePointerPressed;
+            TopRightButton.PointerPressed += OnResizePointerPressed;
+            BottomLeftButton.PointerPressed += OnResizePointerPressed;
+            BottomRightButton.PointerPressed += OnResizePointerPressed;
+            LeftMiddleButton.PointerPressed += OnResizePointerPressed;
+            RightMiddleButton.PointerPressed += OnResizePointerPressed;
+            TopMiddleButton.PointerPressed += OnResizePointerPressed;
+            BottomMiddleButton.PointerPressed += OnResizePointerPressed;
+            
+            TopLeftButton.PointerMoved += OnResizePointerMoved;
+            TopRightButton.PointerMoved += OnResizePointerMoved;
+            BottomLeftButton.PointerMoved += OnResizePointerMoved;
+            BottomRightButton.PointerMoved += OnResizePointerMoved;
+            LeftMiddleButton.PointerMoved += OnResizePointerMoved;
+            RightMiddleButton.PointerMoved += OnResizePointerMoved;
+            TopMiddleButton.PointerMoved += OnResizePointerMoved;
+            BottomMiddleButton.PointerMoved += OnResizePointerMoved;
+            
+            TopLeftButton.PointerReleased += OnResizePointerReleased;
+            TopRightButton.PointerReleased += OnResizePointerReleased;
+            BottomLeftButton.PointerReleased += OnResizePointerReleased;
+            BottomRightButton.PointerReleased += OnResizePointerReleased;
+            LeftMiddleButton.PointerReleased += OnResizePointerReleased;
+            RightMiddleButton.PointerReleased += OnResizePointerReleased;
+            TopMiddleButton.PointerReleased += OnResizePointerReleased;
+            BottomMiddleButton.PointerReleased += OnResizePointerReleased;
         };
     }
 
@@ -48,18 +78,135 @@ public partial class CropControl : UserControl
         Canvas.SetLeft(MainRectangle, vm.SelectionX);
         Canvas.SetTop(MainRectangle, vm.SelectionY);
 
+        // Set buttons positions based on MainRectangle's position
+        UpdateButtonPositions(vm.SelectionX, vm.SelectionY, vm.SelectionWidth, vm.SelectionHeight);
+
         try
         {
             UpdateSurroundingRectangles();
         }
         catch (Exception e)
         {
+#if DEBUG
+            Console.WriteLine(e);
+#endif
+        }
+    }
+    
+    private void OnResizePointerPressed(object? sender, PointerPressedEventArgs e)
+    {
+        if (DataContext is not ImageCropperViewModel vm || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+        {
+            return;
+        }
+
+        // Capture the start position of the mouse and the initial size/position of the rectangle
+        _resizeStart = e.GetPosition(RootCanvas);
+        _originalRect = new Rect(Canvas.GetLeft(MainRectangle), Canvas.GetTop(MainRectangle), vm.SelectionWidth, vm.SelectionHeight);
+
+        _isResizing = true;
+
+        // Determine which button is being dragged
+        if (sender is Border border)
+        {
+            // Store the button being used for resizing (you can check button name later in OnResizePointerMoved)
+            border.Tag = border;
+        }
+    }
+    
+    private void OnResizePointerMoved(object? sender, PointerEventArgs e)
+    {
+        if (!_isResizing || DataContext is not ImageCropperViewModel vm)
+        {
+            return;
+        }
+
+        var currentPos = e.GetPosition(RootCanvas);
+        var delta = currentPos - _resizeStart;
+
+        // Adjust based on the button
+        if (sender is Border border)
+        {
+            var buttonName = border.Name;
+            switch (buttonName)
+            {
+                case "TopLeftButton":
+                    ResizeTopLeft(delta, ref vm);
+                    break;
+                case "TopRightButton":
+                    ResizeTopRight(delta, ref vm);
+                    break;
+                case "BottomLeftButton":
+                    ResizeBottomLeft(delta, ref vm);
+                    break;
+                case "BottomRightButton":
+                    ResizeBottomRight(delta, ref vm);
+                    break;
+                case "LeftMiddleButton":
+                    ResizeLeftMiddle(delta, ref vm);
+                    break;
+                case "RightMiddleButton":
+                    ResizeRightMiddle(delta, ref vm);
+                    break;
+                case "TopMiddleButton":
+                    ResizeTopMiddle(delta, ref vm);
+                    break;
+                case "BottomMiddleButton":
+                    ResizeBottomMiddle(delta, ref vm);
+                    break;
+            }
+
+            UpdateButtonPositions(vm.SelectionX, vm.SelectionY, vm.SelectionWidth, vm.SelectionHeight);
+            UpdateSurroundingRectangles();
+        }
+    }
+    
+    private void UpdateButtonPositions(double selectionX, double selectionY, double selectionWidth, double selectionHeight)
+    {
+        try
+        {
+            // Top Left Button
+            Canvas.SetLeft(TopLeftButton, Convert.ToInt32(selectionX - TopLeftButton.Width / 2));
+            Canvas.SetTop(TopLeftButton, Convert.ToInt32(selectionY - TopLeftButton.Height / 2));
+
+            // Top Right Button
+            Canvas.SetLeft(TopRightButton, Convert.ToInt32(selectionX + selectionWidth - TopRightButton.Width / 2));
+            Canvas.SetTop(TopRightButton, Convert.ToInt32(selectionY - TopRightButton.Height / 2));
+
+            // Top Middle Button
+            Canvas.SetLeft(TopMiddleButton, Convert.ToInt32(selectionX + (selectionWidth / 2) - TopMiddleButton.Width / 2));
+            Canvas.SetTop(TopMiddleButton, Convert.ToInt32(selectionY - TopMiddleButton.Height / 2));
+
+            // Bottom Left Button
+            Canvas.SetLeft(BottomLeftButton, Convert.ToInt32(selectionX - BottomLeftButton.Width / 2));
+            Canvas.SetTop(BottomLeftButton, Convert.ToInt32(selectionY + selectionHeight - BottomLeftButton.Height / 2));
+
+            // Bottom Right Button
+            Canvas.SetLeft(BottomRightButton, Convert.ToInt32(selectionX + selectionWidth - BottomRightButton.Width / 2));
+            Canvas.SetTop(BottomRightButton, Convert.ToInt32(selectionY + selectionHeight - BottomRightButton.Height / 2));
+
+            // Bottom Middle Button
+            Canvas.SetLeft(BottomMiddleButton, Convert.ToInt32(selectionX + (selectionWidth / 2) - BottomMiddleButton.Width / 2));
+            Canvas.SetTop(BottomMiddleButton, Convert.ToInt32(selectionY + selectionHeight - BottomMiddleButton.Height / 2));
+
+            // Left Middle Button
+            Canvas.SetLeft(LeftMiddleButton, Convert.ToInt32(selectionX - LeftMiddleButton.Width / 2));
+            Canvas.SetTop(LeftMiddleButton, Convert.ToInt32(selectionY + (selectionHeight / 2) - LeftMiddleButton.Height / 2));
+
+            // Right Middle Button
+            Canvas.SetLeft(RightMiddleButton, Convert.ToInt32(selectionX + selectionWidth - RightMiddleButton.Width / 2));
+            Canvas.SetTop(RightMiddleButton, Convert.ToInt32(selectionY + (selectionHeight / 2) - RightMiddleButton.Height / 2));
+        }
+        catch (Exception e)
+        {
             #if DEBUG
             Console.WriteLine(e);
             #endif
         }
     }
 
+
+
     private void UpdateSurroundingRectangles()
     {
         if (DataContext is not ImageCropperViewModel vm)
@@ -73,29 +220,27 @@ public partial class CropControl : UserControl
         var right = Convert.ToInt32(left + vm.SelectionWidth);
         var bottom= Convert.ToInt32(top + vm.SelectionHeight);
 
-        // Ensure the left and top values are not negative
-        left = left < 0 ? 0 : left;
-        top = top < 0 ? 0 : top;
-
         // Calculate the positions and sizes for the surrounding rectangles
         // Top Rectangle (above MainRectangle)
         TopRectangle.Width = vm.ImageWidth;
-        TopRectangle.Height = top;
+        TopRectangle.Height = top < 0 ? 0 : top;
         Canvas.SetTop(TopRectangle, 0);
 
         // Bottom Rectangle (below MainRectangle)
         BottomRectangle.Width = vm.ImageWidth;
-        BottomRectangle.Height = vm.ImageHeight - bottom;
+        var newBottomRectangleHeight = vm.ImageHeight - bottom < 0 ? 0 : vm.ImageHeight - bottom;
+        BottomRectangle.Height = newBottomRectangleHeight;
         Canvas.SetTop(BottomRectangle, bottom);
 
         // Left Rectangle (left of MainRectangle)
-        LeftRectangle.Width = left;
+        LeftRectangle.Width = left < 0 ? 0 : left;
         LeftRectangle.Height = vm.SelectionHeight;
         Canvas.SetLeft(LeftRectangle, 0);
         Canvas.SetTop(LeftRectangle, top);
 
         // Right Rectangle (right of MainRectangle)
-        RightRectangle.Width = vm.ImageWidth - right;
+        var newRightRectangleWidth = vm.ImageWidth - right < 0 ? 0 : vm.ImageWidth - right;
+        RightRectangle.Width = newRightRectangleWidth;
         RightRectangle.Height = vm.SelectionHeight;
         Canvas.SetLeft(RightRectangle, right);
         Canvas.SetTop(RightRectangle, top);
@@ -175,6 +320,7 @@ public partial class CropControl : UserControl
         try
         {
             UpdateSurroundingRectangles();
+            UpdateButtonPositions(newLeft, newTop, vm.SelectionWidth, vm.SelectionHeight);
         }
         catch (Exception exception)
         {
@@ -189,4 +335,167 @@ public partial class CropControl : UserControl
     {
         _isDragging = false;
     }
+    
+    private void ResizeTopLeft(Vector delta, ref ImageCropperViewModel vm)
+    {
+        if (vm.SelectionX is 0 && vm.SelectionY is 0)
+        {
+            Cursor = new Cursor(StandardCursorType.Arrow);
+            _isDragging = false;
+            _isResizing = false;
+            return;
+        }
+        // Calculate the new width and height based on the drag delta
+        var newWidth = _originalRect.Width - delta.X;
+        var newHeight = _originalRect.Height - delta.Y;
+
+        // Ensure the rectangle stays within the canvas bounds
+        var newLeft = Math.Max(_originalRect.X + delta.X, 0);
+        var newTop = Math.Max(_originalRect.Y + delta.Y, 0);
+
+        // Constrain the new width and height to not exceed the bounds
+        newWidth = Math.Max(newWidth, 1); // Prevent width from becoming 0 or negative
+        newHeight = Math.Max(newHeight, 1); // Prevent height from becoming 0 or negative
+
+        // Ensure the right and bottom edges don't go beyond the canvas
+        if (newLeft + newWidth > vm.ImageWidth)
+        {
+            newWidth = vm.ImageWidth - newLeft;
+        }
+        if (newTop + newHeight > vm.ImageHeight)
+        {
+            newHeight = vm.ImageHeight - newTop;
+        }
+
+        // Apply the new size and position
+        vm.SelectionX = newLeft;
+        vm.SelectionY = newTop;
+        vm.SelectionWidth = newWidth;
+        vm.SelectionHeight = newHeight;
+        Canvas.SetLeft(MainRectangle, newLeft);
+        Canvas.SetTop(MainRectangle, newTop);
+    }
+
+    private void ResizeTopRight(Vector delta, ref ImageCropperViewModel vm)
+    {
+        var newWidth = _originalRect.Width + delta.X;
+        var newHeight = _originalRect.Height - delta.Y;
+        var newY = _originalRect.Y + delta.Y;
+
+        if (!(newWidth > 0) || !(newHeight > 0))
+        {
+            Cursor = new Cursor(StandardCursorType.Arrow);
+            _isDragging = false;
+            _isResizing = false;
+            return;
+        }
+
+        vm.SelectionWidth = newWidth;
+        vm.SelectionHeight = newHeight;
+        vm.SelectionY = newY;
+        Canvas.SetTop(MainRectangle, newY);
+    }
+
+    private void ResizeBottomLeft(Vector delta, ref ImageCropperViewModel vm)
+    {
+        var newWidth = _originalRect.Width - delta.X;
+        var newHeight = _originalRect.Height + delta.Y;
+        var newX = _originalRect.X + delta.X;
+
+        if (newWidth < 0 || newHeight < 0 || newX < 0)
+        {
+            Cursor = new Cursor(StandardCursorType.Arrow);
+            _isDragging = false;
+            _isResizing = false;
+            return;
+        }
+
+        newWidth = Math.Max(newWidth, 7);
+        newHeight = Math.Max(newHeight, 7);
+
+        vm.SelectionWidth = newWidth;
+        vm.SelectionHeight = newHeight;
+        vm.SelectionX = newX;
+        Canvas.SetLeft(MainRectangle, newX);
+        
+        TooltipHelper.ShowTooltipMessage($"Width: {vm.SelectionWidth}, Height: {vm.SelectionHeight}, x: {vm.SelectionX}, y: {vm.SelectionY}");
+    }
+
+    private void ResizeBottomRight(Vector delta, ref ImageCropperViewModel vm)
+    {
+        // Calculate the new width and height based on the drag delta
+        var newWidth = _originalRect.Width + delta.X;
+        var newHeight = _originalRect.Height + delta.Y;
+
+        // Ensure the new width and height do not exceed the image bounds
+        var newRight = _originalRect.X + newWidth;
+        var newBottom = _originalRect.Y + newHeight;
+
+        if (newRight > vm.ImageWidth)
+        {
+            newWidth = vm.ImageWidth - _originalRect.X;
+        }
+        if (newBottom > vm.ImageHeight)
+        {
+            newHeight = vm.ImageHeight - _originalRect.Y;
+        }
+
+        // Constrain the minimum size
+        newWidth = Math.Max(newWidth, 1);
+        newHeight = Math.Max(newHeight, 1);
+
+        // Apply the new width and height
+        vm.SelectionWidth = newWidth;
+        vm.SelectionHeight = newHeight;
+    }
+
+    private void ResizeLeftMiddle(Vector delta, ref ImageCropperViewModel vm)
+    {
+        var newWidth = _originalRect.Width - delta.X;
+        var newX = _originalRect.X + delta.X;
+
+        if (!(newWidth > 0))
+        {
+            return;
+        }
+
+        vm.SelectionWidth = newWidth;
+        vm.SelectionX = newX;
+        Canvas.SetLeft(MainRectangle, newX);
+    }
+
+    private void ResizeRightMiddle(Vector delta, ref ImageCropperViewModel vm)
+    {
+        var newWidth = _originalRect.Width + delta.X;
+
+        if (newWidth > 0)
+        {
+            vm.SelectionWidth = newWidth;
+        }
+    }
+
+    private void ResizeTopMiddle(Vector delta, ref ImageCropperViewModel vm)
+    {
+        var newHeight = _originalRect.Height - delta.Y;
+        var newY = _originalRect.Y + delta.Y;
+
+        vm.SelectionHeight = newHeight < 0 ? 0 : newHeight;
+        vm.SelectionY = newY < 0 ? 0 : newY;
+        Canvas.SetTop(MainRectangle, newY);
+    }
+
+    private void ResizeBottomMiddle(Vector delta, ref ImageCropperViewModel vm)
+    {
+        var newHeight = _originalRect.Height + delta.Y;
+
+        if (newHeight > 0)
+        {
+            vm.SelectionHeight = newHeight;
+        }
+    }
+    
+    private void OnResizePointerReleased(object? sender, PointerReleasedEventArgs e)
+    {
+        _isResizing = false;
+    }
 }