From ba34bacf291eb6069d85ebd64990d188d0fd7dda Mon Sep 17 00:00:00 2001 From: Christian Schulz Date: Tue, 22 Feb 2022 02:01:49 +0100 Subject: [PATCH] Enhanced ability to crop an image vertically and horizontally. #249 --- src/Greenshot.Editor/Drawing/CropContainer.cs | 180 +++++++++++++++++- .../Drawing/Fields/FieldType.cs | 44 +++-- src/Greenshot.Editor/Drawing/Surface.cs | 143 +++++++++++++- src/Greenshot.Editor/Forms/ImageEditorForm.cs | 20 +- 4 files changed, 353 insertions(+), 34 deletions(-) diff --git a/src/Greenshot.Editor/Drawing/CropContainer.cs b/src/Greenshot.Editor/Drawing/CropContainer.cs index 2324331f0..2a4797760 100644 --- a/src/Greenshot.Editor/Drawing/CropContainer.cs +++ b/src/Greenshot.Editor/Drawing/CropContainer.cs @@ -19,10 +19,12 @@ * along with this program. If not, see . */ + using System.Drawing; using System.Runtime.Serialization; using Greenshot.Base.Interfaces; using Greenshot.Base.Interfaces.Drawing; +using Greenshot.Editor.Drawing.Adorners; using Greenshot.Editor.Drawing.Fields; using Greenshot.Editor.Helpers; @@ -33,6 +35,11 @@ namespace Greenshot.Editor.Drawing /// public class CropContainer : DrawableContainer { + //awailable Styles + public static readonly string DefaultCropStyle = nameof(DefaultCropStyle); + public static readonly string VerticalCropOutStyle = nameof(VerticalCropOutStyle); + public static readonly string HorizontalCropOutStyle = nameof(HorizontalCropOutStyle); + public CropContainer(ISurface parent) : base(parent) { Init(); @@ -45,13 +52,85 @@ namespace Greenshot.Editor.Drawing } private void Init() + { + switch (GetFieldValueAsString(FieldType.CROPSTYLE)) + { + case string s when s.Equals(HorizontalCropOutStyle): + { + InitHorizontalCropOutStyle(); + break; + } + case string s when s.Equals(VerticalCropOutStyle): + { + InitVerticalCropOutStyle(); + break; + } + default: + { + InitCropStyle(); + break; + } + } + } + + /// + /// rotate through all awailable Styles + /// + /// + /// + public static string GetNextStyle(string style) + { + return style switch + { + var s when s.Equals(HorizontalCropOutStyle) => VerticalCropOutStyle, + var s when s.Equals(VerticalCropOutStyle) => DefaultCropStyle, + _ => HorizontalCropOutStyle, + }; + } + + private void InitCropStyle() { CreateDefaultAdorners(); } + private void InitHorizontalCropOutStyle() + { + var defaultHeight = 25; + + if (_parent?.Image is { } image) + { + Size = new Size(image.Width, defaultHeight); + } + CreateTopBottomAdorners(); + } + + private void InitVerticalCropOutStyle() + { + var defaultWidth = 25; + + if (_parent?.Image is { } image) + { + Size = new Size(defaultWidth, image.Height); + } + + CreateLeftRightAdorners(); + } + + private void CreateTopBottomAdorners() + { + Adorners.Add(new ResizeAdorner(this, Positions.TopCenter)); + Adorners.Add(new ResizeAdorner(this, Positions.BottomCenter)); + } + private void CreateLeftRightAdorners() + { + Adorners.Add(new ResizeAdorner(this, Positions.MiddleLeft)); + Adorners.Add(new ResizeAdorner(this, Positions.MiddleRight)); + } + protected override void InitializeFields() { AddField(GetType(), FieldType.FLAGS, FieldFlag.CONFIRMABLE); + AddField(GetType(), FieldType.CROPSTYLE, DefaultCropStyle); } public override void Invalidate() @@ -83,6 +162,7 @@ namespace Greenshot.Editor.Drawing return; } + using Brush cropBrush = new SolidBrush(Color.FromArgb(100, 150, 150, 100)); Rectangle cropRectangle = GuiRectangle.GetGuiRectangle(Left, Top, Width, Height); Rectangle selectionRect = new Rectangle(cropRectangle.Left - 1, cropRectangle.Top - 1, cropRectangle.Width + 1, cropRectangle.Height + 1); @@ -90,20 +170,100 @@ namespace Greenshot.Editor.Drawing DrawSelectionBorder(g, selectionRect); - // top - g.FillRectangle(cropBrush, new Rectangle(0, 0, imageSize.Width, cropRectangle.Top)); - // left - g.FillRectangle(cropBrush, new Rectangle(0, cropRectangle.Top, cropRectangle.Left, cropRectangle.Height)); - // right - g.FillRectangle(cropBrush, - new Rectangle(cropRectangle.Left + cropRectangle.Width, cropRectangle.Top, imageSize.Width - (cropRectangle.Left + cropRectangle.Width), cropRectangle.Height)); - // bottom - g.FillRectangle(cropBrush, new Rectangle(0, cropRectangle.Top + cropRectangle.Height, imageSize.Width, imageSize.Height - (cropRectangle.Top + cropRectangle.Height))); + switch (GetFieldValueAsString(FieldType.CROPSTYLE)) + { + case var s when s.Equals(HorizontalCropOutStyle): + case var t when t.Equals(VerticalCropOutStyle): + { + //draw inside + g.FillRectangle(cropBrush, cropRectangle); + break; + } + default: + { + //draw outside + // top + g.FillRectangle(cropBrush, new Rectangle(0, 0, imageSize.Width, cropRectangle.Top)); + // left + g.FillRectangle(cropBrush, new Rectangle(0, cropRectangle.Top, cropRectangle.Left, cropRectangle.Height)); + // right + g.FillRectangle(cropBrush, new Rectangle(cropRectangle.Left + cropRectangle.Width, cropRectangle.Top, imageSize.Width - (cropRectangle.Left + cropRectangle.Width), cropRectangle.Height)); + // bottom + g.FillRectangle(cropBrush, new Rectangle(0, cropRectangle.Top + cropRectangle.Height, imageSize.Width, imageSize.Height - (cropRectangle.Top + cropRectangle.Height))); + break; + } + } + + } /// /// No context menu for the CropContainer /// public override bool HasContextMenu => false; + + public override bool HandleMouseDown(int x, int y) + { + return GetFieldValueAsString(FieldType.CROPSTYLE) switch + { + //force horizontal crop to left edge + var s when s.Equals(HorizontalCropOutStyle) => base.HandleMouseDown(0, y), + //force vertical crop to top edge + var s when s.Equals(VerticalCropOutStyle) => base.HandleMouseDown(x, 0), + _ => base.HandleMouseDown(x, y), + }; + } + + public override bool HandleMouseMove(int x, int y) + { + Invalidate(); + + switch (GetFieldValueAsString(FieldType.CROPSTYLE)) + { + case var s when s.Equals(HorizontalCropOutStyle): + { + //stick on left and right + //allow only horizontal changes + if (_parent?.Image is { } image) + { + _boundsAfterResize.X = 0; + _boundsAfterResize.Y = _boundsBeforeResize.Top; + _boundsAfterResize.Width = image.Width; + _boundsAfterResize.Height = y - _boundsAfterResize.Top; + } + break; + } + case var s when s.Equals(VerticalCropOutStyle): + { + //stick on top and bottom + //allow only vertical changes + if (_parent?.Image is { } image) + { + _boundsAfterResize.X = _boundsBeforeResize.Left; + _boundsAfterResize.Y = 0; + _boundsAfterResize.Width = x - _boundsAfterResize.Left; + _boundsAfterResize.Height = image.Height; + } + break; + } + default: + { + // reset "workbench" rectangle to current bounds + _boundsAfterResize.X = _boundsBeforeResize.Left; + _boundsAfterResize.Y = _boundsBeforeResize.Top; + _boundsAfterResize.Width = x - _boundsAfterResize.Left; + _boundsAfterResize.Height = y - _boundsAfterResize.Top; + break; + } + + } + ScaleHelper.Scale(_boundsBeforeResize, x, y, ref _boundsAfterResize, GetAngleRoundProcessor()); + + // apply scaled bounds to this DrawableContainer + ApplyBounds(_boundsAfterResize); + + Invalidate(); + return true; + } } -} \ No newline at end of file +} diff --git a/src/Greenshot.Editor/Drawing/Fields/FieldType.cs b/src/Greenshot.Editor/Drawing/Fields/FieldType.cs index c3b732f4b..610cbb7ca 100644 --- a/src/Greenshot.Editor/Drawing/Fields/FieldType.cs +++ b/src/Greenshot.Editor/Drawing/Fields/FieldType.cs @@ -31,31 +31,33 @@ namespace Greenshot.Editor.Drawing.Fields [Serializable] public class FieldType : IFieldType { - public static readonly IFieldType ARROWHEADS = new FieldType("ARROWHEADS"); - public static readonly IFieldType BLUR_RADIUS = new FieldType("BLUR_RADIUS"); - public static readonly IFieldType BRIGHTNESS = new FieldType("BRIGHTNESS"); - public static readonly IFieldType FILL_COLOR = new FieldType("FILL_COLOR"); - public static readonly IFieldType FONT_BOLD = new FieldType("FONT_BOLD"); - public static readonly IFieldType FONT_FAMILY = new FieldType("FONT_FAMILY"); - public static readonly IFieldType FONT_ITALIC = new FieldType("FONT_ITALIC"); - public static readonly IFieldType FONT_SIZE = new FieldType("FONT_SIZE"); - public static readonly IFieldType TEXT_HORIZONTAL_ALIGNMENT = new FieldType("TEXT_HORIZONTAL_ALIGNMENT"); - public static readonly IFieldType TEXT_VERTICAL_ALIGNMENT = new FieldType("TEXT_VERTICAL_ALIGNMENT"); - public static readonly IFieldType HIGHLIGHT_COLOR = new FieldType("HIGHLIGHT_COLOR"); - public static readonly IFieldType LINE_COLOR = new FieldType("LINE_COLOR"); - public static readonly IFieldType LINE_THICKNESS = new FieldType("LINE_THICKNESS"); - public static readonly IFieldType MAGNIFICATION_FACTOR = new FieldType("MAGNIFICATION_FACTOR"); - public static readonly IFieldType PIXEL_SIZE = new FieldType("PIXEL_SIZE"); - public static readonly IFieldType PREVIEW_QUALITY = new FieldType("PREVIEW_QUALITY"); - public static readonly IFieldType SHADOW = new FieldType("SHADOW"); - public static readonly IFieldType PREPARED_FILTER_OBFUSCATE = new FieldType("PREPARED_FILTER_OBFUSCATE"); - public static readonly IFieldType PREPARED_FILTER_HIGHLIGHT = new FieldType("PREPARED_FILTER_HIGHLIGHT"); - public static readonly IFieldType FLAGS = new FieldType("FLAGS"); + public static readonly IFieldType ARROWHEADS = new FieldType(nameof(ARROWHEADS)); + public static readonly IFieldType BLUR_RADIUS = new FieldType(nameof(BLUR_RADIUS)); + public static readonly IFieldType BRIGHTNESS = new FieldType(nameof(BRIGHTNESS)); + public static readonly IFieldType FILL_COLOR = new FieldType(nameof(FILL_COLOR)); + public static readonly IFieldType FONT_BOLD = new FieldType(nameof(FONT_BOLD)); + public static readonly IFieldType FONT_FAMILY = new FieldType(nameof(FONT_FAMILY)); + public static readonly IFieldType FONT_ITALIC = new FieldType(nameof(FONT_ITALIC)); + public static readonly IFieldType FONT_SIZE = new FieldType(nameof(FONT_SIZE)); + public static readonly IFieldType TEXT_HORIZONTAL_ALIGNMENT = new FieldType(nameof(TEXT_HORIZONTAL_ALIGNMENT)); + public static readonly IFieldType TEXT_VERTICAL_ALIGNMENT = new FieldType(nameof(TEXT_VERTICAL_ALIGNMENT)); + public static readonly IFieldType HIGHLIGHT_COLOR = new FieldType(nameof(HIGHLIGHT_COLOR)); + public static readonly IFieldType LINE_COLOR = new FieldType(nameof(LINE_COLOR)); + public static readonly IFieldType LINE_THICKNESS = new FieldType(nameof(LINE_THICKNESS)); + public static readonly IFieldType MAGNIFICATION_FACTOR = new FieldType(nameof(MAGNIFICATION_FACTOR)); + public static readonly IFieldType PIXEL_SIZE = new FieldType(nameof(PIXEL_SIZE)); + public static readonly IFieldType PREVIEW_QUALITY = new FieldType(nameof(PREVIEW_QUALITY)); + public static readonly IFieldType SHADOW = new FieldType(nameof(SHADOW)); + public static readonly IFieldType PREPARED_FILTER_OBFUSCATE = new FieldType(nameof(PREPARED_FILTER_OBFUSCATE)); + public static readonly IFieldType PREPARED_FILTER_HIGHLIGHT = new FieldType(nameof(PREPARED_FILTER_HIGHLIGHT)); + public static readonly IFieldType FLAGS = new FieldType(nameof(FLAGS)); + public static readonly IFieldType CROPSTYLE = new FieldType(nameof(CROPSTYLE)); + public static IFieldType[] Values = { ARROWHEADS, BLUR_RADIUS, BRIGHTNESS, FILL_COLOR, FONT_BOLD, FONT_FAMILY, FONT_ITALIC, FONT_SIZE, TEXT_HORIZONTAL_ALIGNMENT, TEXT_VERTICAL_ALIGNMENT, HIGHLIGHT_COLOR, - LINE_COLOR, LINE_THICKNESS, MAGNIFICATION_FACTOR, PIXEL_SIZE, PREVIEW_QUALITY, SHADOW, PREPARED_FILTER_OBFUSCATE, PREPARED_FILTER_HIGHLIGHT, FLAGS + LINE_COLOR, LINE_THICKNESS, MAGNIFICATION_FACTOR, PIXEL_SIZE, PREVIEW_QUALITY, SHADOW, PREPARED_FILTER_OBFUSCATE, PREPARED_FILTER_HIGHLIGHT, FLAGS, CROPSTYLE }; public string Name { get; set; } diff --git a/src/Greenshot.Editor/Drawing/Surface.cs b/src/Greenshot.Editor/Drawing/Surface.cs index 7c27c8ce1..2979026ab 100644 --- a/src/Greenshot.Editor/Drawing/Surface.cs +++ b/src/Greenshot.Editor/Drawing/Surface.cs @@ -1184,7 +1184,7 @@ namespace Greenshot.Editor.Drawing /// /// Crop the surface /// - /// + /// rectangle that remains /// public bool ApplyCrop(Rectangle cropRectangle) { @@ -1226,6 +1226,112 @@ namespace Greenshot.Editor.Drawing return false; } + /// + /// Crop out the surface + /// Splits the image in 3 parts(top, middle, bottom). Crop out the middle and joins top and bottom. + /// + /// rectangle of the middle part + /// + public bool ApplyHorizontalCrop(Rectangle cropRectangle) + { + if (IsCropPossible(ref cropRectangle)) + { + Rectangle imageRectangle = new Rectangle(Point.Empty, Image.Size); + Bitmap tmpNewimage, tmpImageTop, tmpImageBottom; + // Make sure we have information, this this fails + try + { + tmpNewimage = new Bitmap(Image.Size.Width, Image.Size.Height - cropRectangle.Height); + tmpImageTop = ImageHelper.CloneArea(Image, new Rectangle(0, 0, Image.Size.Width, cropRectangle.Top), PixelFormat.DontCare); + tmpImageBottom = ImageHelper.CloneArea(Image, new Rectangle(0, cropRectangle.Top + cropRectangle.Height, Image.Size.Width, Image.Size.Height - cropRectangle.Top - cropRectangle.Height), PixelFormat.DontCare); + } + catch (Exception ex) + { + ex.Data.Add("CropRectangle", cropRectangle); + ex.Data.Add("Width", Image.Width); + ex.Data.Add("Height", Image.Height); + ex.Data.Add("Pixelformat", Image.PixelFormat); + throw; + } + using Graphics g = Graphics.FromImage(tmpNewimage); + g.DrawImage(tmpImageTop, new Point(0, 0)); + g.DrawImage(tmpImageBottom, new Point(0, tmpImageTop.Height)); + + + Matrix matrix = new Matrix(); + matrix.Translate(0, -(cropRectangle.Top + cropRectangle.Height), MatrixOrder.Append); + // Make undoable + MakeUndoable(new SurfaceBackgroundChangeMemento(this, matrix), false); + + // Do not dispose otherwise we can't undo the image! + SetImage(tmpNewimage, false); + + _elements.Transform(matrix); + if (_surfaceSizeChanged != null && !imageRectangle.Equals(new Rectangle(Point.Empty, tmpNewimage.Size))) + { + _surfaceSizeChanged(this, null); + } + + Invalidate(); + return true; + } + + return false; + } + + /// + /// Crop out the surface + /// Splits the image in 3 parts(left, middle, right). Crop out the middle and joins top and bottom. + /// + /// rectangle of the middle part + /// + public bool ApplyVerticalCrop(Rectangle cropRectangle) + { + if (IsCropPossible(ref cropRectangle)) + { + Rectangle imageRectangle = new Rectangle(Point.Empty, Image.Size); + Bitmap tmpNewimage, tmpImageLeft, tmpImageRight; + // Make sure we have information, this this fails + try + { + tmpNewimage = new Bitmap(Image.Size.Width - cropRectangle.Width, Image.Size.Height); + + tmpImageLeft = ImageHelper.CloneArea(Image, new Rectangle(0, 0, cropRectangle.Left, Image.Size.Height), PixelFormat.DontCare); + tmpImageRight = ImageHelper.CloneArea(Image, new Rectangle(cropRectangle.Left + cropRectangle.Width, 0, Image.Size.Width - cropRectangle.Width - cropRectangle.Left, Image.Size.Height), PixelFormat.DontCare); + } + catch (Exception ex) + { + ex.Data.Add("CropRectangle", cropRectangle); + ex.Data.Add("Width", Image.Width); + ex.Data.Add("Height", Image.Height); + ex.Data.Add("Pixelformat", Image.PixelFormat); + throw; + } + using Graphics g = Graphics.FromImage(tmpNewimage); + g.DrawImage(tmpImageLeft, new Point(0, 0)); + g.DrawImage(tmpImageRight, new Point(tmpImageLeft.Width, 0)); + + Matrix matrix = new Matrix(); + matrix.Translate(- cropRectangle.Left - cropRectangle.Width, 0, MatrixOrder.Append); + // Make undoable + MakeUndoable(new SurfaceBackgroundChangeMemento(this, matrix), false); + + // Do not dispose otherwise we can't undo the image! + SetImage(tmpNewimage, false); + + _elements.Transform(matrix); + if (_surfaceSizeChanged != null && !imageRectangle.Equals(new Rectangle(Point.Empty, tmpNewimage.Size))) + { + _surfaceSizeChanged(this, null); + } + + Invalidate(); + return true; + } + + return false; + } + /// /// The background here is the captured image. /// This is called from the SurfaceBackgroundChangeMemento. @@ -1961,7 +2067,31 @@ namespace Greenshot.Editor.Drawing RemoveElement(_cropContainer, false); if (confirm) { - ApplyCrop(_cropContainer.Bounds); + if (dc is CropContainer e) + { + switch (e.GetFieldValueAsString(FieldType.CROPSTYLE)) + { + case var s when s.Equals(CropContainer.HorizontalCropOutStyle): + { + ApplyHorizontalCrop(_cropContainer.Bounds); + break; + } + case var s when s.Equals(CropContainer.VerticalCropOutStyle): + { + ApplyVerticalCrop(_cropContainer.Bounds); + break; + } + default: + { + ApplyCrop(_cropContainer.Bounds); + break; + } + } + } + else + { + ApplyCrop(_cropContainer.Bounds); + } } _cropContainer.Dispose(); @@ -1971,6 +2101,15 @@ namespace Greenshot.Editor.Drawing } } + public void RemoveCropContainer() + { + if (_cropContainer != null) + { + RemoveElement(_cropContainer, false); + _cropContainer.Dispose(); + _cropContainer = null; + } + } /// /// Paste all the elements that are on the clipboard /// diff --git a/src/Greenshot.Editor/Forms/ImageEditorForm.cs b/src/Greenshot.Editor/Forms/ImageEditorForm.cs index 8ab283d0b..277f04252 100644 --- a/src/Greenshot.Editor/Forms/ImageEditorForm.cs +++ b/src/Greenshot.Editor/Forms/ImageEditorForm.cs @@ -727,7 +727,20 @@ namespace Greenshot.Editor.Forms private void BtnCropClick(object sender, EventArgs e) { - _surface.DrawingMode = DrawingModes.Crop; + if (_surface.DrawingMode == DrawingModes.Crop) + { + //intercept repeated click event + //rotate through crop styles + _surface.FieldAggregator.GetField(FieldType.CROPSTYLE).Value = CropContainer.GetNextStyle((string)_surface.FieldAggregator.GetField(FieldType.CROPSTYLE).Value); + _surface.RemoveCropContainer(); + + //reinitialize crop modus + _surface.DrawingMode = DrawingModes.Crop; + } + else + { + _surface.DrawingMode = DrawingModes.Crop; + } RefreshFieldControls(); } @@ -1379,6 +1392,11 @@ namespace Greenshot.Editor.Forms ToolStripItemEndisabler.Enable(helpToolStripMenuItem); ToolStripItemEndisabler.Enable(aboutToolStripMenuItem); ToolStripItemEndisabler.Enable(preferencesToolStripMenuItem); + if (_surface.DrawingMode == DrawingModes.Crop) + { + //While cropping, enable the button to change crop style + btnCrop.Enabled = true; + } _controlsDisabledDueToConfirmable = true; } }