Enhanced ability to crop an image vertically and horizontally. #249

This commit is contained in:
Christian Schulz 2022-02-22 02:01:49 +01:00
commit ba34bacf29
4 changed files with 353 additions and 34 deletions

View file

@ -19,10 +19,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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
/// </summary>
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;
}
}
}
/// <summary>
/// rotate through all awailable Styles
/// </summary>
/// <param name="style"></param>
/// <returns></returns>
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;
}
}
}
/// <summary>
/// No context menu for the CropContainer
/// </summary>
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;
}
}
}

View file

@ -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; }

View file

@ -1184,7 +1184,7 @@ namespace Greenshot.Editor.Drawing
/// <summary>
/// Crop the surface
/// </summary>
/// <param name="cropRectangle"></param>
/// <param name="cropRectangle">rectangle that remains</param>
/// <returns></returns>
public bool ApplyCrop(Rectangle cropRectangle)
{
@ -1226,6 +1226,112 @@ namespace Greenshot.Editor.Drawing
return false;
}
/// <summary>
/// Crop out the surface
/// Splits the image in 3 parts(top, middle, bottom). Crop out the middle and joins top and bottom.
/// </summary>
/// <param name="cropRectangle">rectangle of the middle part</param>
/// <returns></returns>
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;
}
/// <summary>
/// Crop out the surface
/// Splits the image in 3 parts(left, middle, right). Crop out the middle and joins top and bottom.
/// </summary>
/// <param name="cropRectangle">rectangle of the middle part</param>
/// <returns></returns>
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;
}
/// <summary>
/// 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;
}
}
/// <summary>
/// Paste all the elements that are on the clipboard
/// </summary>

View file

@ -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;
}
}