From b1f7441176411d9404076e03da702ccb393fc30a Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Tue, 16 Aug 2022 00:22:54 +0200 Subject: [PATCH 01/23] Feature #4513, initial prototype --- ShareX.HelpersLib/Helpers/ImageHelpers.cs | 64 ++++++++ ShareX.ScreenCaptureLib/Enums.cs | 3 +- .../Shapes/ShapeManager.cs | 35 ++++ .../Shapes/Tool/TrimInteriorTool.cs | 154 ++++++++++++++++++ .../ShareX.ScreenCaptureLib.csproj | 1 + 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 ShareX.ScreenCaptureLib/Shapes/Tool/TrimInteriorTool.cs diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs index fe8454afb..abe8dc17e 100644 --- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs +++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs @@ -223,6 +223,70 @@ public static Bitmap CropBitmap(Bitmap bmp, Rectangle rect) return null; } + public static Bitmap TrimBitmapInteriorHorizontal(Bitmap bmp, int x, int width) + { + if (bmp != null && width > 0) + { + Bitmap leftPart = null, rightPart = null; + if (x > 0) + { + leftPart = CropBitmap(bmp, new Rectangle(0, 0, Math.Min(x, bmp.Width), bmp.Height)); + } + if (x + width < bmp.Width) + { + int x2 = Math.Max(x + width, 0); + rightPart = CropBitmap(bmp, new Rectangle(x2, 0, bmp.Width - x2, bmp.Height)); + } + + if (leftPart != null && rightPart != null) + { + return CombineImages(new List { leftPart, rightPart }, Orientation.Horizontal); + } + else if (leftPart != null) + { + return leftPart; + } + else if (rightPart != null) + { + return rightPart; + } + } + + return null; + } + + public static Bitmap TrimBitmapInteriorVertical(Bitmap bmp, int y, int height) + { + if (bmp != null && height > 0) + { + Bitmap topPart = null, bottomPart = null; + if (y > 0) + { + topPart = CropBitmap(bmp, new Rectangle(0, 0, bmp.Width, Math.Min(y, bmp.Height))); + } + if (y + height < bmp.Height) + { + int y2 = Math.Max(y + height, 0); + bottomPart = CropBitmap(bmp, new Rectangle(0, y2, bmp.Width, bmp.Height - y2)); + } + + if (topPart != null && bottomPart != null) + { + return CombineImages(new List { topPart, bottomPart }, Orientation.Vertical); + } + else if (topPart != null) + { + return topPart; + } + else if (bottomPart != null) + { + return bottomPart; + } + } + + return null; + } + /// Automatically crop image to remove transparent outside area. public static Bitmap AutoCropTransparent(Bitmap bmp) { diff --git a/ShareX.ScreenCaptureLib/Enums.cs b/ShareX.ScreenCaptureLib/Enums.cs index c576902f1..501c2352e 100644 --- a/ShareX.ScreenCaptureLib/Enums.cs +++ b/ShareX.ScreenCaptureLib/Enums.cs @@ -291,7 +291,8 @@ public enum ShapeType // Localized EffectBlur, EffectPixelate, EffectHighlight, - ToolCrop + ToolCrop, + ToolTrimInterior } public enum ScrollingCaptureScrollMethod // Localized diff --git a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs index 565fda74f..bc7bc7d05 100644 --- a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs +++ b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs @@ -1195,6 +1195,9 @@ private BaseShape CreateShape(ShapeType shapeType) case ShapeType.ToolCrop: shape = new CropTool(); break; + case ShapeType.ToolTrimInterior: + shape = new TrimInteriorTool(); + break; } shape.Manager = this; @@ -1620,6 +1623,16 @@ public void MoveAll(PointF offset) MoveAll(offset.X, offset.Y); } + public void CollapseAllHorizontal(float x, float width) + { + // todo + } + + public void CollapseAllVertical(float y, float height) + { + // todo + } + public void RemoveOutsideShapes() { foreach (BaseShape shape in Shapes.ToArray()) @@ -1807,6 +1820,28 @@ public Bitmap CropImage(RectangleF rect, bool onlyIfSizeDifferent = false) return null; } + public void TrimInterior(RectangleF rect) + { + bool isHorizontal = rect.Width > rect.Height; + + rect = CaptureHelpers.ScreenToClient(rect.Round()); + PointF offset = CaptureHelpers.ScreenToClient(Form.CanvasRectangle.Location.Round()); + rect.X -= offset.X; + rect.Y -= offset.Y; + Rectangle cropRect = Rectangle.Intersect(new Rectangle(0, 0, Form.Canvas.Width, Form.Canvas.Height), rect.Round()); + + if (isHorizontal && cropRect.Width > 0) + { + CollapseAllHorizontal(rect.X, rect.Width); + UpdateCanvas(ImageHelpers.TrimBitmapInteriorHorizontal(Form.Canvas, cropRect.X, cropRect.Width)); + } + else if (!isHorizontal && cropRect.Height > 0) + { + CollapseAllVertical(rect.Y, rect.Height); + UpdateCanvas(ImageHelpers.TrimBitmapInteriorVertical(Form.Canvas, cropRect.Y, cropRect.Height)); + } + } + public Color GetColor(Bitmap bmp, Point pos) { if (bmp != null) diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/TrimInteriorTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/TrimInteriorTool.cs new file mode 100644 index 000000000..fcebc6a16 --- /dev/null +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/TrimInteriorTool.cs @@ -0,0 +1,154 @@ +#region License Information (GPL v3) + +/* + ShareX - A program that allows you to take screenshots and share any file type + Copyright (c) 2007-2022 ShareX Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Optionally you can also view the license at . +*/ + +#endregion License Information (GPL v3) + +using ShareX.HelpersLib; +using System.Drawing; +using System.Windows.Forms; + +namespace ShareX.ScreenCaptureLib +{ + public class TrimInteriorTool : BaseTool + { + public override ShapeType ShapeType { get; } = ShapeType.ToolTrimInterior; + + public override bool LimitRectangleToInsideCanvas { get; } = true; + + public bool IsHorizontalTrim => Rectangle.Width >= Options.MinimumSize && Rectangle.Width > Rectangle.Height; + public bool IsVerticalTrim => Rectangle.Height >= Options.MinimumSize && Rectangle.Height >= Rectangle.Width; + + public override bool IsValidShape => IsHorizontalTrim || IsVerticalTrim; + + private ImageEditorButton confirmButton, cancelButton; + private Size buttonSize = new Size(80, 40); + private int buttonOffset = 15; + + private Brush EffectBrush = new SolidBrush(Color.FromArgb(128, Color.Gray)); + + public override void OnUpdate() + { + base.OnUpdate(); + + if (confirmButton != null && cancelButton != null) + { + if (Rectangle.Bottom + buttonOffset + buttonSize.Height > Manager.Form.ClientArea.Bottom && + Rectangle.Width > (buttonSize.Width * 2) + (buttonOffset * 3) && + Rectangle.Height > buttonSize.Height + (buttonOffset * 2)) + { + confirmButton.Rectangle = new RectangleF(Rectangle.Right - (buttonOffset * 2) - (buttonSize.Width * 2), + Rectangle.Bottom - buttonOffset - buttonSize.Height, buttonSize.Width, buttonSize.Height); + cancelButton.Rectangle = new RectangleF(Rectangle.Right - buttonOffset - buttonSize.Width, + Rectangle.Bottom - buttonOffset - buttonSize.Height, buttonSize.Width, buttonSize.Height); + } + else + { + confirmButton.Rectangle = new RectangleF(Rectangle.Right - (buttonSize.Width * 2) - buttonOffset, + Rectangle.Bottom + buttonOffset, buttonSize.Width, buttonSize.Height); + cancelButton.Rectangle = new RectangleF(Rectangle.Right - buttonSize.Width, + Rectangle.Bottom + buttonOffset, buttonSize.Width, buttonSize.Height); + } + } + } + + public override void OnDraw(Graphics g) + { + if (IsHorizontalTrim) + { + g.FillRectangle(EffectBrush, new RectangleF(Rectangle.X, g.ClipBounds.Y, Rectangle.Width, g.ClipBounds.Height)); + } + else if (IsVerticalTrim) + { + g.FillRectangle(EffectBrush, new RectangleF(g.ClipBounds.X, Rectangle.Y, g.ClipBounds.Width, Rectangle.Height)); + } + } + + public override void OnCreated() + { + confirmButton = new ImageEditorButton() + { + Text = "\u2714", + ButtonColor = Color.ForestGreen, + Rectangle = new Rectangle(new Point(), buttonSize), + Visible = true + }; + confirmButton.MouseDown += ConfirmButton_MousePressed; + confirmButton.MouseEnter += () => Manager.Form.Cursor = Cursors.Hand; + confirmButton.MouseLeave += () => Manager.Form.SetDefaultCursor(); + Manager.DrawableObjects.Add(confirmButton); + + cancelButton = new ImageEditorButton() + { + Text = "\u2716", + ButtonColor = Color.FromArgb(227, 45, 45), + Rectangle = new Rectangle(new Point(), buttonSize), + Visible = true + }; + cancelButton.MouseDown += CancelButton_MousePressed; + cancelButton.MouseEnter += () => Manager.Form.Cursor = Cursors.Hand; + cancelButton.MouseLeave += () => Manager.Form.SetDefaultCursor(); + Manager.DrawableObjects.Add(cancelButton); + } + + private void ConfirmButton_MousePressed(object sender, MouseEventArgs e) + { + Manager.TrimInterior(Rectangle); + Remove(); + } + + private void CancelButton_MousePressed(object sender, MouseEventArgs e) + { + Remove(); + } + + public override void Remove() + { + base.Remove(); + + if (Options.SwitchToSelectionToolAfterDrawing) + { + Manager.CurrentTool = ShapeType.ToolSelect; + } + } + + public override void Dispose() + { + base.Dispose(); + + if ((confirmButton != null && confirmButton.IsCursorHover) || (cancelButton != null && cancelButton.IsCursorHover)) + { + Manager.Form.SetDefaultCursor(); + } + + if (confirmButton != null) + { + Manager.DrawableObjects.Remove(confirmButton); + } + + if (cancelButton != null) + { + Manager.DrawableObjects.Remove(cancelButton); + } + } + } +} diff --git a/ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj b/ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj index de0e210bc..f3c05b926 100644 --- a/ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj +++ b/ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj @@ -226,6 +226,7 @@ + From d5f522ab80610a92dea25506157462c7696d59f1 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Tue, 16 Aug 2022 12:11:13 +0200 Subject: [PATCH 02/23] Rename TrimInteriorTool to CutOutTool --- ShareX.HelpersLib/Helpers/ImageHelpers.cs | 4 ++-- ShareX.ScreenCaptureLib/Enums.cs | 2 +- ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs | 10 +++++----- .../Shapes/Tool/{TrimInteriorTool.cs => CutOutTool.cs} | 6 +++--- ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) rename ShareX.ScreenCaptureLib/Shapes/Tool/{TrimInteriorTool.cs => CutOutTool.cs} (98%) diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs index abe8dc17e..f82fc256d 100644 --- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs +++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs @@ -223,7 +223,7 @@ public static Bitmap CropBitmap(Bitmap bmp, Rectangle rect) return null; } - public static Bitmap TrimBitmapInteriorHorizontal(Bitmap bmp, int x, int width) + public static Bitmap CutOutBitmapMiddleHorizontal(Bitmap bmp, int x, int width) { if (bmp != null && width > 0) { @@ -255,7 +255,7 @@ public static Bitmap TrimBitmapInteriorHorizontal(Bitmap bmp, int x, int width) return null; } - public static Bitmap TrimBitmapInteriorVertical(Bitmap bmp, int y, int height) + public static Bitmap CutOutBitmapMiddleVertical(Bitmap bmp, int y, int height) { if (bmp != null && height > 0) { diff --git a/ShareX.ScreenCaptureLib/Enums.cs b/ShareX.ScreenCaptureLib/Enums.cs index 501c2352e..710295771 100644 --- a/ShareX.ScreenCaptureLib/Enums.cs +++ b/ShareX.ScreenCaptureLib/Enums.cs @@ -292,7 +292,7 @@ public enum ShapeType // Localized EffectPixelate, EffectHighlight, ToolCrop, - ToolTrimInterior + ToolCutOut } public enum ScrollingCaptureScrollMethod // Localized diff --git a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs index bc7bc7d05..293d75d3a 100644 --- a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs +++ b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs @@ -1195,8 +1195,8 @@ private BaseShape CreateShape(ShapeType shapeType) case ShapeType.ToolCrop: shape = new CropTool(); break; - case ShapeType.ToolTrimInterior: - shape = new TrimInteriorTool(); + case ShapeType.ToolCutOut: + shape = new CutOutTool(); break; } @@ -1820,7 +1820,7 @@ public Bitmap CropImage(RectangleF rect, bool onlyIfSizeDifferent = false) return null; } - public void TrimInterior(RectangleF rect) + public void CutOut(RectangleF rect) { bool isHorizontal = rect.Width > rect.Height; @@ -1833,12 +1833,12 @@ public void TrimInterior(RectangleF rect) if (isHorizontal && cropRect.Width > 0) { CollapseAllHorizontal(rect.X, rect.Width); - UpdateCanvas(ImageHelpers.TrimBitmapInteriorHorizontal(Form.Canvas, cropRect.X, cropRect.Width)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddleHorizontal(Form.Canvas, cropRect.X, cropRect.Width)); } else if (!isHorizontal && cropRect.Height > 0) { CollapseAllVertical(rect.Y, rect.Height); - UpdateCanvas(ImageHelpers.TrimBitmapInteriorVertical(Form.Canvas, cropRect.Y, cropRect.Height)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddleVertical(Form.Canvas, cropRect.Y, cropRect.Height)); } } diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/TrimInteriorTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs similarity index 98% rename from ShareX.ScreenCaptureLib/Shapes/Tool/TrimInteriorTool.cs rename to ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs index fcebc6a16..b4bbbeffd 100644 --- a/ShareX.ScreenCaptureLib/Shapes/Tool/TrimInteriorTool.cs +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs @@ -29,9 +29,9 @@ namespace ShareX.ScreenCaptureLib { - public class TrimInteriorTool : BaseTool + public class CutOutTool : BaseTool { - public override ShapeType ShapeType { get; } = ShapeType.ToolTrimInterior; + public override ShapeType ShapeType { get; } = ShapeType.ToolCutOut; public override bool LimitRectangleToInsideCanvas { get; } = true; @@ -112,7 +112,7 @@ public override void OnCreated() private void ConfirmButton_MousePressed(object sender, MouseEventArgs e) { - Manager.TrimInterior(Rectangle); + Manager.CutOut(Rectangle); Remove(); } diff --git a/ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj b/ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj index f3c05b926..26ef6e832 100644 --- a/ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj +++ b/ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj @@ -226,7 +226,7 @@ - + From 8b0c5d0d6613d4196cf114997d9a5c12d27cac26 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Tue, 16 Aug 2022 19:14:19 +0200 Subject: [PATCH 03/23] Confirm/Cancel button positioning logic for Cut Out tool --- .../Shapes/Tool/CutOutTool.cs | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs index b4bbbeffd..50cb6fea3 100644 --- a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs @@ -52,21 +52,55 @@ public override void OnUpdate() if (confirmButton != null && cancelButton != null) { - if (Rectangle.Bottom + buttonOffset + buttonSize.Height > Manager.Form.ClientArea.Bottom && - Rectangle.Width > (buttonSize.Width * 2) + (buttonOffset * 3) && - Rectangle.Height > buttonSize.Height + (buttonOffset * 2)) + if (IsVerticalTrim) { - confirmButton.Rectangle = new RectangleF(Rectangle.Right - (buttonOffset * 2) - (buttonSize.Width * 2), - Rectangle.Bottom - buttonOffset - buttonSize.Height, buttonSize.Width, buttonSize.Height); - cancelButton.Rectangle = new RectangleF(Rectangle.Right - buttonOffset - buttonSize.Width, - Rectangle.Bottom - buttonOffset - buttonSize.Height, buttonSize.Width, buttonSize.Height); + float spaceBelow = Manager.Form.ClientArea.Bottom - Rectangle.Bottom; + bool positionBelow = spaceBelow >= buttonSize.Height + 2 * buttonOffset; + float buttonsTop = positionBelow ? Rectangle.Bottom + buttonOffset : Rectangle.Top - buttonOffset - buttonSize.Height; + float buttonsLeft = Rectangle.Left + Rectangle.Width / 2 - (2 * buttonSize.Width + buttonOffset) / 2; + float buttonsRight = buttonsLeft + 2 * buttonSize.Width + buttonOffset; + bool overflowsLeft = buttonsLeft < Manager.Form.ClientArea.Left + buttonOffset; + bool overflowsRight = buttonsRight >= Manager.Form.ClientArea.Right - buttonOffset; + if (overflowsLeft && overflowsRight) + { + // can't fix + } + else if (overflowsLeft) + { + buttonsLeft = Manager.Form.ClientArea.Left + buttonOffset; + } + else if (overflowsRight) + { + buttonsRight = Manager.Form.ClientArea.Right - buttonOffset; + buttonsLeft = buttonsRight - 2 * buttonSize.Width - buttonOffset; + } + confirmButton.Rectangle = new RectangleF(buttonsLeft, buttonsTop, buttonSize.Width, buttonSize.Height); + cancelButton.Rectangle = confirmButton.Rectangle.LocationOffset(buttonSize.Width + buttonOffset, 0); } else { - confirmButton.Rectangle = new RectangleF(Rectangle.Right - (buttonSize.Width * 2) - buttonOffset, - Rectangle.Bottom + buttonOffset, buttonSize.Width, buttonSize.Height); - cancelButton.Rectangle = new RectangleF(Rectangle.Right - buttonSize.Width, - Rectangle.Bottom + buttonOffset, buttonSize.Width, buttonSize.Height); + float spaceRight = Manager.Form.ClientArea.Right - Rectangle.Right; + bool positionRight = spaceRight >= buttonSize.Width + 2 * buttonOffset; + float buttonsLeft = positionRight ? Rectangle.Right + buttonOffset : Rectangle.Left - buttonOffset - buttonSize.Width; + float buttonsTop = Rectangle.Top + Rectangle.Height / 2 - (2 * buttonSize.Height + buttonOffset) / 2; + float buttonsBottom = buttonsTop + 2 * buttonSize.Height + buttonOffset; + bool overflowsTop = buttonsTop < Manager.Form.ClientArea.Top + buttonOffset; + bool overflowsBottom = buttonsBottom >= Manager.Form.ClientArea.Bottom - buttonOffset; + if (overflowsTop && overflowsBottom) + { + // can't fix + } + else if (overflowsTop) + { + buttonsTop = Manager.Form.ClientArea.Top + buttonOffset; + } + else if (overflowsBottom) + { + buttonsBottom = Manager.Form.ClientArea.Bottom - buttonOffset; + buttonsTop = buttonsBottom - 2 * buttonSize.Height - buttonOffset; + } + confirmButton.Rectangle = new RectangleF(buttonsLeft, buttonsTop, buttonSize.Width, buttonSize.Height); + cancelButton.Rectangle = confirmButton.Rectangle.LocationOffset(0, buttonSize.Height + buttonOffset); } } } From 32cae726e8742fbec6750ac0fbbea3f724c54c61 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Tue, 16 Aug 2022 23:25:20 +0200 Subject: [PATCH 04/23] Implement collapsing shapes overlapping with cut out area --- .../Shapes/ShapeManager.cs | 110 +++++++++++++++++- 1 file changed, 104 insertions(+), 6 deletions(-) diff --git a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs index 293d75d3a..a080e640d 100644 --- a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs +++ b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs @@ -1625,12 +1625,110 @@ public void MoveAll(PointF offset) public void CollapseAllHorizontal(float x, float width) { - // todo + float x2 = x + width; + if (width <= 0) return; + + List toDelete = new List(); + + foreach (BaseShape shape in Shapes) + { + RectangleF sr = shape.Rectangle; + if (sr.Left < x) + { + if (sr.Right <= x) + { + // case 1: entirely before the cut, no action needed + } + else if (sr.Right < x2) + { + // case 2: end reaches into the cut, shorten shape to end at x + shape.Rectangle = new RectangleF(sr.X, sr.Y, x - sr.X, sr.Height); + } + else + { + // case 3: end reaches over the cut, shorten shape by width, keeping left + shape.Rectangle = new RectangleF(sr.X, sr.Y, sr.Width - width, sr.Height); + } + } + else if (sr.Left < x2) + { + if (sr.Right <= x2) + { + // case 4: entirely inside the cut, delete the shape + toDelete.Add(shape); + } + else + { + // case 5: beginning reaches into the cut, shorten shape by difference between shape left and x2 + shape.Rectangle = new RectangleF(x, sr.Y, sr.Right - x2, sr.Height); + } + } + else + { + // case 6: entirely after the cut, offset shape by width + shape.Rectangle = new RectangleF(sr.X - width, sr.Y, sr.Width, sr.Height); + } + } + + foreach (BaseShape shape in toDelete) + { + shape.Dispose(); + Shapes.Remove(shape); + } } public void CollapseAllVertical(float y, float height) { - // todo + float y2 = y + height; + if (height <= 0) return; + + List toDelete = new List(); + + foreach (BaseShape shape in Shapes) + { + RectangleF sr = shape.Rectangle; + if (sr.Top < y) + { + if (sr.Bottom <= y) + { + // case 1: entirely before the cut, no action needed + } + else if (sr.Bottom < y2) + { + // case 2: end reaches into the cut, shorten shape to end at x + shape.Rectangle = new RectangleF(sr.X, sr.Y, sr.Width, y - sr.Y); + } + else + { + // case 3: end reaches over the cut, shorten shape by width, keeping left + shape.Rectangle = new RectangleF(sr.X, sr.Y, sr.Width, sr.Height - height); + } + } + else if (sr.Top < y2) + { + if (sr.Bottom <= y2) + { + // case 4: entirely inside the cut, delete the shape + toDelete.Add(shape); + } + else + { + // case 5: beginning reaches into the cut, shorten shape by difference between shape left and x2 + shape.Rectangle = new RectangleF(sr.X, y, sr.Width, sr.Bottom - y2); + } + } + else + { + // case 6: entirely after the cut, offset shape by width + shape.Rectangle = new RectangleF(sr.X, sr.Y - height, sr.Width, sr.Height); + } + } + + foreach (BaseShape shape in toDelete) + { + shape.Dispose(); + Shapes.Remove(shape); + } } public void RemoveOutsideShapes() @@ -1824,11 +1922,11 @@ public void CutOut(RectangleF rect) { bool isHorizontal = rect.Width > rect.Height; - rect = CaptureHelpers.ScreenToClient(rect.Round()); + RectangleF adjustedRect = CaptureHelpers.ScreenToClient(rect.Round()); PointF offset = CaptureHelpers.ScreenToClient(Form.CanvasRectangle.Location.Round()); - rect.X -= offset.X; - rect.Y -= offset.Y; - Rectangle cropRect = Rectangle.Intersect(new Rectangle(0, 0, Form.Canvas.Width, Form.Canvas.Height), rect.Round()); + adjustedRect.X -= offset.X; + adjustedRect.Y -= offset.Y; + Rectangle cropRect = Rectangle.Intersect(new Rectangle(0, 0, Form.Canvas.Width, Form.Canvas.Height), adjustedRect.Round()); if (isHorizontal && cropRect.Width > 0) { From ac0cfcd4ec530cd13e3f44c92fd5973536eee7ee Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Tue, 16 Aug 2022 23:46:00 +0200 Subject: [PATCH 05/23] Proof of concept Torn Edges effect on cut --- ShareX.HelpersLib/Enums.cs | 9 +++ ShareX.HelpersLib/Helpers/ImageHelpers.cs | 60 ++++++++++++++++++- .../Shapes/ShapeManager.cs | 4 +- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/ShareX.HelpersLib/Enums.cs b/ShareX.HelpersLib/Enums.cs index 59fe208ab..6ef449cdd 100644 --- a/ShareX.HelpersLib/Enums.cs +++ b/ShareX.HelpersLib/Enums.cs @@ -212,4 +212,13 @@ public enum StepType // Localized RomanNumeralsUppercase, RomanNumeralsLowercase } + + public enum CutOutEffectType // Localized (probably) + { + None, + ZigZag, + TornEdge, + Wave, + Gradient + } } \ No newline at end of file diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs index f82fc256d..6a2b78798 100644 --- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs +++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs @@ -223,7 +223,7 @@ public static Bitmap CropBitmap(Bitmap bmp, Rectangle rect) return null; } - public static Bitmap CutOutBitmapMiddleHorizontal(Bitmap bmp, int x, int width) + public static Bitmap CutOutBitmapMiddleHorizontal(Bitmap bmp, int x, int width, CutOutEffectType effectType, int effectSize) { if (bmp != null && width > 0) { @@ -231,11 +231,39 @@ public static Bitmap CutOutBitmapMiddleHorizontal(Bitmap bmp, int x, int width) if (x > 0) { leftPart = CropBitmap(bmp, new Rectangle(0, 0, Math.Min(x, bmp.Width), bmp.Height)); + switch (effectType) + { + case CutOutEffectType.None: + break; + case CutOutEffectType.ZigZag: + break; + case CutOutEffectType.TornEdge: + leftPart = TornEdges(leftPart, effectSize, effectSize * 2, AnchorStyles.Right, false); + break; + case CutOutEffectType.Wave: + break; + case CutOutEffectType.Gradient: + break; + } } if (x + width < bmp.Width) { int x2 = Math.Max(x + width, 0); rightPart = CropBitmap(bmp, new Rectangle(x2, 0, bmp.Width - x2, bmp.Height)); + switch (effectType) + { + case CutOutEffectType.None: + break; + case CutOutEffectType.ZigZag: + break; + case CutOutEffectType.TornEdge: + rightPart = TornEdges(rightPart, effectSize, effectSize * 2, AnchorStyles.Left, false); + break; + case CutOutEffectType.Wave: + break; + case CutOutEffectType.Gradient: + break; + } } if (leftPart != null && rightPart != null) @@ -255,7 +283,7 @@ public static Bitmap CutOutBitmapMiddleHorizontal(Bitmap bmp, int x, int width) return null; } - public static Bitmap CutOutBitmapMiddleVertical(Bitmap bmp, int y, int height) + public static Bitmap CutOutBitmapMiddleVertical(Bitmap bmp, int y, int height, CutOutEffectType effectType, int effectSize) { if (bmp != null && height > 0) { @@ -263,11 +291,39 @@ public static Bitmap CutOutBitmapMiddleVertical(Bitmap bmp, int y, int height) if (y > 0) { topPart = CropBitmap(bmp, new Rectangle(0, 0, bmp.Width, Math.Min(y, bmp.Height))); + switch (effectType) + { + case CutOutEffectType.None: + break; + case CutOutEffectType.ZigZag: + break; + case CutOutEffectType.TornEdge: + topPart = TornEdges(topPart, effectSize, effectSize * 2, AnchorStyles.Bottom, false); + break; + case CutOutEffectType.Wave: + break; + case CutOutEffectType.Gradient: + break; + } } if (y + height < bmp.Height) { int y2 = Math.Max(y + height, 0); bottomPart = CropBitmap(bmp, new Rectangle(0, y2, bmp.Width, bmp.Height - y2)); + switch (effectType) + { + case CutOutEffectType.None: + break; + case CutOutEffectType.ZigZag: + break; + case CutOutEffectType.TornEdge: + bottomPart = TornEdges(bottomPart, effectSize, effectSize * 2, AnchorStyles.Top, false); + break; + case CutOutEffectType.Wave: + break; + case CutOutEffectType.Gradient: + break; + } } if (topPart != null && bottomPart != null) diff --git a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs index a080e640d..3820579dd 100644 --- a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs +++ b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs @@ -1931,12 +1931,12 @@ public void CutOut(RectangleF rect) if (isHorizontal && cropRect.Width > 0) { CollapseAllHorizontal(rect.X, rect.Width); - UpdateCanvas(ImageHelpers.CutOutBitmapMiddleHorizontal(Form.Canvas, cropRect.X, cropRect.Width)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddleHorizontal(Form.Canvas, cropRect.X, cropRect.Width, CutOutEffectType.TornEdge, 5)); } else if (!isHorizontal && cropRect.Height > 0) { CollapseAllVertical(rect.Y, rect.Height); - UpdateCanvas(ImageHelpers.CutOutBitmapMiddleVertical(Form.Canvas, cropRect.Y, cropRect.Height)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddleVertical(Form.Canvas, cropRect.Y, cropRect.Height, CutOutEffectType.TornEdge, 5)); } } From b43c374d86bd633f89fea316df84d1e77d363be2 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 08:49:45 +0200 Subject: [PATCH 06/23] Properly use DeleteShape --- ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs index 3820579dd..168c469a1 100644 --- a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs +++ b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs @@ -1672,8 +1672,7 @@ public void CollapseAllHorizontal(float x, float width) foreach (BaseShape shape in toDelete) { - shape.Dispose(); - Shapes.Remove(shape); + DeleteShape(shape); } } @@ -1726,8 +1725,7 @@ public void CollapseAllVertical(float y, float height) foreach (BaseShape shape in toDelete) { - shape.Dispose(); - Shapes.Remove(shape); + DeleteShape(shape); } } From 2465e942067890d0766d34b9ae0521d741bedc3c Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 15:54:54 +0200 Subject: [PATCH 07/23] Fix selection highlight brush --- ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs index 50cb6fea3..7ff65feb1 100644 --- a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs @@ -44,7 +44,7 @@ public class CutOutTool : BaseTool private Size buttonSize = new Size(80, 40); private int buttonOffset = 15; - private Brush EffectBrush = new SolidBrush(Color.FromArgb(128, Color.Gray)); + private Brush selectionHighlightBrush = new SolidBrush(Color.FromArgb(128, Color.Gray)); public override void OnUpdate() { @@ -109,11 +109,11 @@ public override void OnDraw(Graphics g) { if (IsHorizontalTrim) { - g.FillRectangle(EffectBrush, new RectangleF(Rectangle.X, g.ClipBounds.Y, Rectangle.Width, g.ClipBounds.Height)); + g.FillRectangle(selectionHighlightBrush, new RectangleF(Rectangle.X, g.ClipBounds.Y, Rectangle.Width, g.ClipBounds.Height)); } else if (IsVerticalTrim) { - g.FillRectangle(EffectBrush, new RectangleF(g.ClipBounds.X, Rectangle.Y, g.ClipBounds.Width, Rectangle.Height)); + g.FillRectangle(selectionHighlightBrush, new RectangleF(g.ClipBounds.X, Rectangle.Y, g.ClipBounds.Width, Rectangle.Height)); } } @@ -169,6 +169,8 @@ public override void Dispose() { base.Dispose(); + selectionHighlightBrush.Dispose(); + if ((confirmButton != null && confirmButton.IsCursorHover) || (cancelButton != null && cancelButton.IsCursorHover)) { Manager.Form.SetDefaultCursor(); From 9de1953e6d686f4a8a36e3c58ca9e39b0a0c64dd Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 16:13:55 +0200 Subject: [PATCH 08/23] Refactor CutOutBitmapMiddle --- ShareX.HelpersLib/Helpers/ImageHelpers.cs | 144 ++++++------------ .../Shapes/ShapeManager.cs | 4 +- 2 files changed, 48 insertions(+), 100 deletions(-) diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs index 6a2b78798..403785b27 100644 --- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs +++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs @@ -223,124 +223,72 @@ public static Bitmap CropBitmap(Bitmap bmp, Rectangle rect) return null; } - public static Bitmap CutOutBitmapMiddleHorizontal(Bitmap bmp, int x, int width, CutOutEffectType effectType, int effectSize) + private static Bitmap ApplyCutOutEffect(Bitmap bmp, AnchorStyles effectEdge, CutOutEffectType effectType, int effectSize) { - if (bmp != null && width > 0) + switch (effectType) { - Bitmap leftPart = null, rightPart = null; - if (x > 0) - { - leftPart = CropBitmap(bmp, new Rectangle(0, 0, Math.Min(x, bmp.Width), bmp.Height)); - switch (effectType) - { - case CutOutEffectType.None: - break; - case CutOutEffectType.ZigZag: - break; - case CutOutEffectType.TornEdge: - leftPart = TornEdges(leftPart, effectSize, effectSize * 2, AnchorStyles.Right, false); - break; - case CutOutEffectType.Wave: - break; - case CutOutEffectType.Gradient: - break; - } - } - if (x + width < bmp.Width) - { - int x2 = Math.Max(x + width, 0); - rightPart = CropBitmap(bmp, new Rectangle(x2, 0, bmp.Width - x2, bmp.Height)); - switch (effectType) - { - case CutOutEffectType.None: - break; - case CutOutEffectType.ZigZag: - break; - case CutOutEffectType.TornEdge: - rightPart = TornEdges(rightPart, effectSize, effectSize * 2, AnchorStyles.Left, false); - break; - case CutOutEffectType.Wave: - break; - case CutOutEffectType.Gradient: - break; - } - } + case CutOutEffectType.None: + return bmp; - if (leftPart != null && rightPart != null) - { - return CombineImages(new List { leftPart, rightPart }, Orientation.Horizontal); - } - else if (leftPart != null) - { - return leftPart; - } - else if (rightPart != null) - { - return rightPart; - } + case CutOutEffectType.ZigZag: + return bmp; + + case CutOutEffectType.TornEdge: + return TornEdges(bmp, effectSize, effectSize * 2, effectEdge, false); + + case CutOutEffectType.Wave: + return bmp; + + case CutOutEffectType.Gradient: + return bmp; } - return null; + throw new NotImplementedException(); // should not be reachable } - public static Bitmap CutOutBitmapMiddleVertical(Bitmap bmp, int y, int height, CutOutEffectType effectType, int effectSize) + public static Bitmap CutOutBitmapMiddle(Bitmap bmp, Orientation orientation, int start, int size, CutOutEffectType effectType, int effectSize) { - if (bmp != null && height > 0) + if (bmp != null && size > 0) { - Bitmap topPart = null, bottomPart = null; - if (y > 0) + Bitmap firstPart = null, secondPart = null; + + if (start > 0) { - topPart = CropBitmap(bmp, new Rectangle(0, 0, bmp.Width, Math.Min(y, bmp.Height))); - switch (effectType) - { - case CutOutEffectType.None: - break; - case CutOutEffectType.ZigZag: - break; - case CutOutEffectType.TornEdge: - topPart = TornEdges(topPart, effectSize, effectSize * 2, AnchorStyles.Bottom, false); - break; - case CutOutEffectType.Wave: - break; - case CutOutEffectType.Gradient: - break; - } - } - if (y + height < bmp.Height) - { - int y2 = Math.Max(y + height, 0); - bottomPart = CropBitmap(bmp, new Rectangle(0, y2, bmp.Width, bmp.Height - y2)); - switch (effectType) - { - case CutOutEffectType.None: - break; - case CutOutEffectType.ZigZag: - break; - case CutOutEffectType.TornEdge: - bottomPart = TornEdges(bottomPart, effectSize, effectSize * 2, AnchorStyles.Top, false); - break; - case CutOutEffectType.Wave: - break; - case CutOutEffectType.Gradient: - break; - } + Rectangle r = orientation == Orientation.Horizontal + ? new Rectangle(0, 0, Math.Min(start, bmp.Width), bmp.Height) + : new Rectangle(0, 0, bmp.Width, Math.Min(start, bmp.Height)); + firstPart = CropBitmap(bmp, r); + AnchorStyles effectEdge = orientation == Orientation.Horizontal ? AnchorStyles.Right : AnchorStyles.Bottom; + firstPart = ApplyCutOutEffect(firstPart, effectEdge, effectType, effectSize); } - if (topPart != null && bottomPart != null) + int cutDimension = orientation == Orientation.Horizontal ? bmp.Width : bmp.Height; + if (start + size < cutDimension) { - return CombineImages(new List { topPart, bottomPart }, Orientation.Vertical); + int end = Math.Max(start + size, 0); + Rectangle r = orientation == Orientation.Horizontal + ? new Rectangle(end, 0, bmp.Width - end, bmp.Height) + : new Rectangle(0, end, bmp.Width, bmp.Height - end); + secondPart = CropBitmap(bmp, r); + AnchorStyles effectEdge = orientation == Orientation.Horizontal ? AnchorStyles.Left : AnchorStyles.Top; + secondPart = ApplyCutOutEffect(secondPart, effectEdge, effectType, effectSize); } - else if (topPart != null) + + if (firstPart != null && secondPart != null) { - return topPart; + return CombineImages(new List { firstPart, secondPart }, orientation); } - else if (bottomPart != null) + else if (firstPart != null) { - return bottomPart; + return firstPart; + } + else if (secondPart != null) + { + return secondPart; } } - return null; + return bmp; } /// Automatically crop image to remove transparent outside area. diff --git a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs index 168c469a1..9199912f8 100644 --- a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs +++ b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs @@ -1929,12 +1929,12 @@ public void CutOut(RectangleF rect) if (isHorizontal && cropRect.Width > 0) { CollapseAllHorizontal(rect.X, rect.Width); - UpdateCanvas(ImageHelpers.CutOutBitmapMiddleHorizontal(Form.Canvas, cropRect.X, cropRect.Width, CutOutEffectType.TornEdge, 5)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Horizontal, cropRect.X, cropRect.Width, CutOutEffectType.TornEdge, 5)); } else if (!isHorizontal && cropRect.Height > 0) { CollapseAllVertical(rect.Y, rect.Height); - UpdateCanvas(ImageHelpers.CutOutBitmapMiddleVertical(Form.Canvas, cropRect.Y, cropRect.Height, CutOutEffectType.TornEdge, 5)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Vertical, cropRect.Y, cropRect.Height, CutOutEffectType.TornEdge, 5)); } } From ae25410d83bb2fa14a1c0e856fced9a06c86dab4 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 16:15:18 +0200 Subject: [PATCH 09/23] CutOutEffectType should probably be localized at some point --- ShareX.HelpersLib/Enums.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ShareX.HelpersLib/Enums.cs b/ShareX.HelpersLib/Enums.cs index 6ef449cdd..90264703e 100644 --- a/ShareX.HelpersLib/Enums.cs +++ b/ShareX.HelpersLib/Enums.cs @@ -213,7 +213,7 @@ public enum StepType // Localized RomanNumeralsLowercase } - public enum CutOutEffectType // Localized (probably) + public enum CutOutEffectType // TODO: localize { None, ZigZag, From f9d144400eb4be222e32b5def6bd28ea8213d1fe Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 16:42:00 +0200 Subject: [PATCH 10/23] Use a checker pattern to denote area to be cut out --- .../Shapes/Tool/CutOutTool.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs index 7ff65feb1..f2f8927ea 100644 --- a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs @@ -44,8 +44,6 @@ public class CutOutTool : BaseTool private Size buttonSize = new Size(80, 40); private int buttonOffset = 15; - private Brush selectionHighlightBrush = new SolidBrush(Color.FromArgb(128, Color.Gray)); - public override void OnUpdate() { base.OnUpdate(); @@ -107,13 +105,17 @@ public override void OnUpdate() public override void OnDraw(Graphics g) { - if (IsHorizontalTrim) + using (Image selectionHighlightPattern = ImageHelpers.CreateCheckerPattern(8, 8, Color.FromArgb(128, Color.White), Color.FromArgb(128, Color.Gray))) + using (Brush selectionHighlightBrush = new TextureBrush(selectionHighlightPattern, System.Drawing.Drawing2D.WrapMode.Tile)) { - g.FillRectangle(selectionHighlightBrush, new RectangleF(Rectangle.X, g.ClipBounds.Y, Rectangle.Width, g.ClipBounds.Height)); - } - else if (IsVerticalTrim) - { - g.FillRectangle(selectionHighlightBrush, new RectangleF(g.ClipBounds.X, Rectangle.Y, g.ClipBounds.Width, Rectangle.Height)); + if (IsHorizontalTrim) + { + g.FillRectangle(selectionHighlightBrush, new RectangleF(Rectangle.X, g.ClipBounds.Y, Rectangle.Width, g.ClipBounds.Height)); + } + else if (IsVerticalTrim) + { + g.FillRectangle(selectionHighlightBrush, new RectangleF(g.ClipBounds.X, Rectangle.Y, g.ClipBounds.Width, Rectangle.Height)); + } } } @@ -169,8 +171,6 @@ public override void Dispose() { base.Dispose(); - selectionHighlightBrush.Dispose(); - if ((confirmButton != null && confirmButton.IsCursorHover) || (cancelButton != null && cancelButton.IsCursorHover)) { Manager.Form.SetDefaultCursor(); From ce02e9ab07dad864a5dda7070e247674cf805aea Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 17:07:44 +0200 Subject: [PATCH 11/23] Prevent cuts that would remove the entire image And also don't show resize nodes --- ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs index f2f8927ea..ef2520ff9 100644 --- a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs @@ -38,12 +38,24 @@ public class CutOutTool : BaseTool public bool IsHorizontalTrim => Rectangle.Width >= Options.MinimumSize && Rectangle.Width > Rectangle.Height; public bool IsVerticalTrim => Rectangle.Height >= Options.MinimumSize && Rectangle.Height >= Rectangle.Width; - public override bool IsValidShape => IsHorizontalTrim || IsVerticalTrim; + public override bool IsValidShape { + get + { + if (!IsHorizontalTrim && !IsVerticalTrim) return false; + if (IsHorizontalTrim && Rectangle.Left <= Manager.Form.CanvasRectangle.Left && Rectangle.Right >= Manager.Form.CanvasRectangle.Right) return false; + if (IsVerticalTrim && Rectangle.Top <= Manager.Form.CanvasRectangle.Top && Rectangle.Bottom >= Manager.Form.CanvasRectangle.Bottom) return false; + return true; + } + } private ImageEditorButton confirmButton, cancelButton; private Size buttonSize = new Size(80, 40); private int buttonOffset = 15; + public override void ShowNodes() + { + } + public override void OnUpdate() { base.OnUpdate(); From 21bb827d0344f4614ed21badfe644999f2919e4b Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 17:14:33 +0200 Subject: [PATCH 12/23] Limit cut out selection highlight to canvas area --- ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs index ef2520ff9..76a6277c5 100644 --- a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs @@ -24,6 +24,7 @@ #endregion License Information (GPL v3) using ShareX.HelpersLib; +using System; using System.Drawing; using System.Windows.Forms; @@ -122,11 +123,11 @@ public override void OnDraw(Graphics g) { if (IsHorizontalTrim) { - g.FillRectangle(selectionHighlightBrush, new RectangleF(Rectangle.X, g.ClipBounds.Y, Rectangle.Width, g.ClipBounds.Height)); + g.FillRectangle(selectionHighlightBrush, new RectangleF(Rectangle.X, Math.Max(g.ClipBounds.Y, Manager.Form.CanvasRectangle.Y), Rectangle.Width, Math.Min(g.ClipBounds.Height, Manager.Form.CanvasRectangle.Height))); } else if (IsVerticalTrim) { - g.FillRectangle(selectionHighlightBrush, new RectangleF(g.ClipBounds.X, Rectangle.Y, g.ClipBounds.Width, Rectangle.Height)); + g.FillRectangle(selectionHighlightBrush, new RectangleF(Math.Max(g.ClipBounds.X, Manager.Form.CanvasRectangle.X), Rectangle.Y, Math.Min(g.ClipBounds.Width, Manager.Form.CanvasRectangle.Width), Rectangle.Height)); } } } From a342f8d2b6c4437e4c6c3d7a9d55df4cf2f02213 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 18:24:02 +0200 Subject: [PATCH 13/23] Add Wave cut effect --- ShareX.HelpersLib/Helpers/ImageHelpers.cs | 87 ++++++++++++++++++- .../Shapes/ShapeManager.cs | 4 +- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs index 403785b27..a198b9da7 100644 --- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs +++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs @@ -237,7 +237,7 @@ private static Bitmap ApplyCutOutEffect(Bitmap bmp, AnchorStyles effectEdge, Cut return TornEdges(bmp, effectSize, effectSize * 2, effectEdge, false); case CutOutEffectType.Wave: - return bmp; + return WavyEdges(bmp, effectSize, effectSize * 5, effectEdge); case CutOutEffectType.Gradient: return bmp; @@ -1664,6 +1664,91 @@ public static void FastBoxBlur(Bitmap bmp, int radius) } } + public static Bitmap WavyEdges(Bitmap bmp, int waveDepth, int waveRange, AnchorStyles sides) + { + if (waveDepth < 1 || waveRange < 1 || sides == AnchorStyles.None) + { + return bmp; + } + + List points = new List(); + + int horizontalWaveCount = Math.Max(2, (bmp.Width / waveRange + 1) / 2 * 2) - 1; + int verticalWaveCount = Math.Max(2, (bmp.Height / waveRange + 1) / 2 * 2) - 1; + + Bitmap updateResult(Bitmap bmpIn, Point[] path) + { + Bitmap bmpResult = bmpIn.CreateEmptyBitmap(); + using (bmpIn) + using (Graphics g = Graphics.FromImage(bmpResult)) + using (TextureBrush brush = new TextureBrush(bmpIn)) + { + g.SetHighQuality(); + g.FillPolygon(brush, path); + } + return bmpResult; + }; + + int waveFunction(int t, int max, int depth) => (int)((1 - Math.Cos(t * Math.PI / max)) * depth / 2); + + if (sides.HasFlag(AnchorStyles.Top) && horizontalWaveCount > 1) + { + waveRange = bmp.Width / horizontalWaveCount; + points.Clear(); + for (int x = 0; x < bmp.Width; x += waveDepth) + { + points.Add(new Point(x, waveFunction(x, waveRange, waveDepth))); + } + points.Add(new Point(bmp.Width - 1, waveFunction(bmp.Width - 1, waveRange, waveDepth))); + points.Add(new Point(bmp.Width - 1, bmp.Height - 1)); + points.Add(new Point(0, bmp.Height - 1)); + bmp = updateResult(bmp, points.ToArray()); + } + + if (sides.HasFlag(AnchorStyles.Right) && verticalWaveCount > 1) + { + waveRange = bmp.Height / verticalWaveCount; + points.Clear(); + points.Add(new Point(0, 0)); + for (int y = 0; y < bmp.Height; y += waveDepth) + { + points.Add(new Point(bmp.Width - 1 - waveDepth + waveFunction(y, waveRange, waveDepth), y)); + } + points.Add(new Point(bmp.Width - 1 - waveDepth + waveFunction(bmp.Height - 1, waveRange, waveDepth), bmp.Height - 1)); + points.Add(new Point(0, bmp.Height - 1)); + bmp = updateResult(bmp, points.ToArray()); + } + + if (sides.HasFlag(AnchorStyles.Bottom) && horizontalWaveCount > 1) + { + waveRange = bmp.Width / horizontalWaveCount; + points.Clear(); + points.Add(new Point(0, 0)); + points.Add(new Point(bmp.Width - 1, 0)); + for (int x = bmp.Width - 1; x >= 0; x -= waveDepth) + { + points.Add(new Point(x, bmp.Height - 1 - waveDepth + waveFunction(x, waveRange, waveDepth))); + } + points.Add(new Point(0, bmp.Height - 1 - waveDepth + waveFunction(0, waveRange, waveDepth))); + bmp = updateResult(bmp, points.ToArray()); + } + + if (sides.HasFlag(AnchorStyles.Left) && verticalWaveCount > 1) + { + waveRange = bmp.Height / verticalWaveCount; + points.Add(new Point(0, 0)); + points.Add(new Point(bmp.Width - 1, 0)); + points.Add(new Point(bmp.Width - 1, bmp.Height - 1)); + for (int y = bmp.Height - 1; y >= 0; y -= waveDepth) + { + points.Add(new Point(waveFunction(y, waveRange, waveDepth), y)); + } + bmp = updateResult(bmp, points.ToArray()); + } + + return bmp; + } + public static Bitmap TornEdges(Bitmap bmp, int tornDepth, int tornRange, AnchorStyles sides, bool curvedEdges) { if (tornDepth < 1 || tornRange < 1 || sides == AnchorStyles.None) diff --git a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs index 9199912f8..60723e5fa 100644 --- a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs +++ b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs @@ -1929,12 +1929,12 @@ public void CutOut(RectangleF rect) if (isHorizontal && cropRect.Width > 0) { CollapseAllHorizontal(rect.X, rect.Width); - UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Horizontal, cropRect.X, cropRect.Width, CutOutEffectType.TornEdge, 5)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Horizontal, cropRect.X, cropRect.Width, CutOutEffectType.Wave, 10)); } else if (!isHorizontal && cropRect.Height > 0) { CollapseAllVertical(rect.Y, rect.Height); - UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Vertical, cropRect.Y, cropRect.Height, CutOutEffectType.TornEdge, 5)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Vertical, cropRect.Y, cropRect.Height, CutOutEffectType.Wave, 10)); } } From e0423cf7c00069990d15d207e17525bfdb341bd8 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 19:01:12 +0200 Subject: [PATCH 14/23] Implement ZigZag effect via a flag for TornEdges function This reworks the TornEdges function to give a more consistent result at the corners too. --- ShareX.HelpersLib/Helpers/ImageHelpers.cs | 38 ++++++++++--------- ShareX.ImageEffectsLib/Filters/TornEdge.cs | 2 +- .../Shapes/ShapeManager.cs | 4 +- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs index a198b9da7..1f4702144 100644 --- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs +++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs @@ -231,10 +231,10 @@ private static Bitmap ApplyCutOutEffect(Bitmap bmp, AnchorStyles effectEdge, Cut return bmp; case CutOutEffectType.ZigZag: - return bmp; + return TornEdges(bmp, effectSize, effectSize, effectEdge, false, false); case CutOutEffectType.TornEdge: - return TornEdges(bmp, effectSize, effectSize * 2, effectEdge, false); + return TornEdges(bmp, effectSize, effectSize * 2, effectEdge, false, true); case CutOutEffectType.Wave: return WavyEdges(bmp, effectSize, effectSize * 5, effectEdge); @@ -1749,7 +1749,7 @@ Bitmap updateResult(Bitmap bmpIn, Point[] path) return bmp; } - public static Bitmap TornEdges(Bitmap bmp, int tornDepth, int tornRange, AnchorStyles sides, bool curvedEdges) + public static Bitmap TornEdges(Bitmap bmp, int tornDepth, int tornRange, AnchorStyles sides, bool curvedEdges, bool random) { if (tornDepth < 1 || tornRange < 1 || sides == AnchorStyles.None) { @@ -1768,53 +1768,57 @@ public static Bitmap TornEdges(Bitmap bmp, int tornDepth, int tornRange, AnchorS if (sides.HasFlag(AnchorStyles.Top) && horizontalTornCount > 1) { - for (int x = 0; x < horizontalTornCount - 1; x++) + for (int x = 0; x < bmp.Width; x += tornRange) { - points.Add(new Point(tornRange * x, RandomFast.Next(0, tornDepth))); + int y = random ? RandomFast.Next(0, tornDepth) : ((x / tornRange) & 1) * tornDepth; + points.Add(new Point(x, y)); } } else { points.Add(new Point(0, 0)); - points.Add(new Point(bmp.Width - 1, 0)); + points.Add(new Point(bmp.Width, 0)); } if (sides.HasFlag(AnchorStyles.Right) && verticalTornCount > 1) { - for (int y = 0; y < verticalTornCount - 1; y++) + for (int y = 0; y < bmp.Height; y += tornRange) { - points.Add(new Point(bmp.Width - 1 - RandomFast.Next(0, tornDepth), tornRange * y)); + int x = random ? RandomFast.Next(0, tornDepth) : ((y / tornRange) & 1) * tornDepth; + points.Add(new Point(bmp.Width - tornDepth + x, y)); } } else { - points.Add(new Point(bmp.Width - 1, 0)); - points.Add(new Point(bmp.Width - 1, bmp.Height - 1)); + points.Add(new Point(bmp.Width, 0)); + points.Add(new Point(bmp.Width, bmp.Height)); } if (sides.HasFlag(AnchorStyles.Bottom) && horizontalTornCount > 1) { - for (int x = 0; x < horizontalTornCount - 1; x++) + for (int x = bmp.Width; x >= 0; x = (x / tornRange - 1) * tornRange) { - points.Add(new Point(bmp.Width - 1 - (tornRange * x), bmp.Height - 1 - RandomFast.Next(0, tornDepth))); + int y = random ? RandomFast.Next(0, tornDepth) : ((x / tornRange) & 1) * tornDepth; + points.Add(new Point(x, bmp.Height - tornDepth + y)); } } else { - points.Add(new Point(bmp.Width - 1, bmp.Height - 1)); - points.Add(new Point(0, bmp.Height - 1)); + points.Add(new Point(bmp.Width, bmp.Height)); + points.Add(new Point(0, bmp.Height)); } if (sides.HasFlag(AnchorStyles.Left) && verticalTornCount > 1) { - for (int y = 0; y < verticalTornCount - 1; y++) + for (int y = bmp.Height; y >= 0; y = (y / tornRange - 1) * tornRange) { - points.Add(new Point(RandomFast.Next(0, tornDepth), bmp.Height - 1 - (tornRange * y))); + int x = random ? RandomFast.Next(0, tornDepth) : ((y / tornRange) & 1) * tornDepth; + points.Add(new Point(x, y)); } } else { - points.Add(new Point(0, bmp.Height - 1)); + points.Add(new Point(0, bmp.Height)); points.Add(new Point(0, 0)); } diff --git a/ShareX.ImageEffectsLib/Filters/TornEdge.cs b/ShareX.ImageEffectsLib/Filters/TornEdge.cs index 44c6c9a02..cd5a8303e 100644 --- a/ShareX.ImageEffectsLib/Filters/TornEdge.cs +++ b/ShareX.ImageEffectsLib/Filters/TornEdge.cs @@ -52,7 +52,7 @@ public TornEdge() public override Bitmap Apply(Bitmap bmp) { - return ImageHelpers.TornEdges(bmp, Depth, Range, Sides, CurvedEdges); + return ImageHelpers.TornEdges(bmp, Depth, Range, Sides, CurvedEdges, true); } } } \ No newline at end of file diff --git a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs index 60723e5fa..114fa0c38 100644 --- a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs +++ b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs @@ -1929,12 +1929,12 @@ public void CutOut(RectangleF rect) if (isHorizontal && cropRect.Width > 0) { CollapseAllHorizontal(rect.X, rect.Width); - UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Horizontal, cropRect.X, cropRect.Width, CutOutEffectType.Wave, 10)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Horizontal, cropRect.X, cropRect.Width, CutOutEffectType.ZigZag, 5)); } else if (!isHorizontal && cropRect.Height > 0) { CollapseAllVertical(rect.Y, rect.Height); - UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Vertical, cropRect.Y, cropRect.Height, CutOutEffectType.Wave, 10)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Vertical, cropRect.Y, cropRect.Height, CutOutEffectType.ZigZag, 5)); } } From 8ca64af5961a051f001b6dafe86c468fb9c2c599 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 21:04:04 +0200 Subject: [PATCH 15/23] Remove the Gradient effect type for now It's troublesome to implement and will probably look kind of weird anyway. --- ShareX.HelpersLib/Enums.cs | 3 +-- ShareX.HelpersLib/Helpers/ImageHelpers.cs | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/ShareX.HelpersLib/Enums.cs b/ShareX.HelpersLib/Enums.cs index 90264703e..4b7076fec 100644 --- a/ShareX.HelpersLib/Enums.cs +++ b/ShareX.HelpersLib/Enums.cs @@ -218,7 +218,6 @@ public enum CutOutEffectType // TODO: localize None, ZigZag, TornEdge, - Wave, - Gradient + Wave } } \ No newline at end of file diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs index 1f4702144..0ab2f3be9 100644 --- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs +++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs @@ -238,9 +238,6 @@ private static Bitmap ApplyCutOutEffect(Bitmap bmp, AnchorStyles effectEdge, Cut case CutOutEffectType.Wave: return WavyEdges(bmp, effectSize, effectSize * 5, effectEdge); - - case CutOutEffectType.Gradient: - return bmp; } throw new NotImplementedException(); // should not be reachable From b784ebe0c7fafec0349cbaf7d4fabc83f92748bd Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 21:46:45 +0200 Subject: [PATCH 16/23] Add GUI configuration for cut out tool --- .../Properties/Resources.Designer.cs | 18 +++++++++++ .../Properties/Resources.resx | 6 ++++ .../Shapes/AnnotationOptions.cs | 4 +++ .../Shapes/ShapeManager.cs | 4 +-- .../Shapes/ShapeManagerMenu.cs | 31 +++++++++++++++++-- 5 files changed, 59 insertions(+), 4 deletions(-) diff --git a/ShareX.ScreenCaptureLib/Properties/Resources.Designer.cs b/ShareX.ScreenCaptureLib/Properties/Resources.Designer.cs index 5f0ad7767..8403c6deb 100644 --- a/ShareX.ScreenCaptureLib/Properties/Resources.Designer.cs +++ b/ShareX.ScreenCaptureLib/Properties/Resources.Designer.cs @@ -284,6 +284,24 @@ internal class Resources { } } + /// + /// Looks up a localized string similar to Cut cut effect size. + /// + internal static string CutOutEffectSize { + get { + return ResourceManager.GetString("CutOutEffectSize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cut out effect. + /// + internal static string CutOutEffectType { + get { + return ResourceManager.GetString("CutOutEffectType", resourceCulture); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/ShareX.ScreenCaptureLib/Properties/Resources.resx b/ShareX.ScreenCaptureLib/Properties/Resources.resx index b53dc11d1..80a4af0c3 100644 --- a/ShareX.ScreenCaptureLib/Properties/Resources.resx +++ b/ShareX.ScreenCaptureLib/Properties/Resources.resx @@ -805,4 +805,10 @@ X: {4} Y: {5} CRF: @Invariant + + Cut cut effect size + + + Cut out effect + \ No newline at end of file diff --git a/ShareX.ScreenCaptureLib/Shapes/AnnotationOptions.cs b/ShareX.ScreenCaptureLib/Shapes/AnnotationOptions.cs index 2a269f585..1dbbf1c09 100644 --- a/ShareX.ScreenCaptureLib/Shapes/AnnotationOptions.cs +++ b/ShareX.ScreenCaptureLib/Shapes/AnnotationOptions.cs @@ -106,5 +106,9 @@ public class AnnotationOptions // Highlight effect public Color HighlightColor { get; set; } = Color.Yellow; + + // Cut Out tool + public CutOutEffectType CutOutEffectType { get; set; } = CutOutEffectType.None; + public int CutOutEffectSize { get; set; } = 5; } } \ No newline at end of file diff --git a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs index 114fa0c38..297c5261b 100644 --- a/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs +++ b/ShareX.ScreenCaptureLib/Shapes/ShapeManager.cs @@ -1929,12 +1929,12 @@ public void CutOut(RectangleF rect) if (isHorizontal && cropRect.Width > 0) { CollapseAllHorizontal(rect.X, rect.Width); - UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Horizontal, cropRect.X, cropRect.Width, CutOutEffectType.ZigZag, 5)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Horizontal, cropRect.X, cropRect.Width, AnnotationOptions.CutOutEffectType, AnnotationOptions.CutOutEffectSize)); } else if (!isHorizontal && cropRect.Height > 0) { CollapseAllVertical(rect.Y, rect.Height); - UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Vertical, cropRect.Y, cropRect.Height, CutOutEffectType.ZigZag, 5)); + UpdateCanvas(ImageHelpers.CutOutBitmapMiddle(Form.Canvas, Orientation.Vertical, cropRect.Y, cropRect.Height, AnnotationOptions.CutOutEffectType, AnnotationOptions.CutOutEffectSize)); } } diff --git a/ShareX.ScreenCaptureLib/Shapes/ShapeManagerMenu.cs b/ShareX.ScreenCaptureLib/Shapes/ShapeManagerMenu.cs index effb600cc..eb85204df 100644 --- a/ShareX.ScreenCaptureLib/Shapes/ShapeManagerMenu.cs +++ b/ShareX.ScreenCaptureLib/Shapes/ShapeManagerMenu.cs @@ -51,9 +51,9 @@ internal partial class ShapeManager private ToolStripMenuItem tsmiShadow, tsmiShadowColor, tsmiUndo, tsmiDuplicate, tsmiDelete, tsmiDeleteAll, tsmiMoveTop, tsmiMoveUp, tsmiMoveDown, tsmiMoveBottom, tsmiRegionCapture, tsmiQuickCrop, tsmiShowMagnifier; private ToolStripLabeledNumericUpDown tslnudBorderSize, tslnudCornerRadius, tslnudCenterPoints, tslnudBlurRadius, tslnudPixelateSize, tslnudStepFontSize, - tslnudMagnifierPixelCount, tslnudStartingStepValue, tslnudMagnifyStrength; + tslnudMagnifierPixelCount, tslnudStartingStepValue, tslnudMagnifyStrength, tslnudCutOutEffectSize; private ToolStripLabel tslDragLeft, tslDragRight; - private ToolStripLabeledComboBox tscbBorderStyle, tscbArrowHeadDirection, tscbImageInterpolationMode, tscbCursorTypes, tscbStepType; + private ToolStripLabeledComboBox tscbBorderStyle, tscbArrowHeadDirection, tscbImageInterpolationMode, tscbCursorTypes, tscbStepType, tscbCutOutEffectType; internal void CreateToolbar() { @@ -642,6 +642,26 @@ internal void CreateToolbar() }; tsddbShapeOptions.DropDownItems.Add(tsmiShadowColor); + tscbCutOutEffectType = new ToolStripLabeledComboBox(Resources.CutOutEffectType); + tscbCutOutEffectType.Content.AddRange(Helpers.GetLocalizedEnumDescriptions()); + tscbCutOutEffectType.Content.SelectedIndexChanged += (sender, e) => + { + AnnotationOptions.CutOutEffectType = (CutOutEffectType)tscbCutOutEffectType.Content.SelectedIndex; + tscbCutOutEffectType.Invalidate(); + UpdateCurrentShape(); + }; + tsddbShapeOptions.DropDownItems.Add(tscbCutOutEffectType); + + tslnudCutOutEffectSize = new ToolStripLabeledNumericUpDown(Resources.CutOutEffectSize); + tslnudCutOutEffectSize.Content.Minimum = 3; + tslnudCutOutEffectSize.Content.Maximum = 100; + tslnudCutOutEffectSize.Content.ValueChanged = (sender, e) => + { + AnnotationOptions.CutOutEffectSize = (int)tslnudCutOutEffectSize.Content.Value; + UpdateCurrentShape(); + }; + tsddbShapeOptions.DropDownItems.Add(tslnudCutOutEffectSize); + // In dropdown menu if only last item is visible then menu opens at 0, 0 position on first open, so need to add dummy item to solve this weird bug... tsddbShapeOptions.DropDownItems.Add(new ToolStripSeparator() { Visible = false }); @@ -1456,6 +1476,10 @@ private void UpdateMenu() tscbArrowHeadDirection.Content.SelectedIndex = (int)AnnotationOptions.ArrowHeadDirection; + tscbCutOutEffectType.Content.SelectedIndex = (int)AnnotationOptions.CutOutEffectType; + + tslnudCutOutEffectSize.Content.Value = AnnotationOptions.CutOutEffectSize; + switch (shapeType) { default: @@ -1477,6 +1501,7 @@ private void UpdateMenu() case ShapeType.DrawingCursor: case ShapeType.EffectBlur: case ShapeType.EffectPixelate: + case ShapeType.ToolCutOut: tsddbShapeOptions.Visible = true; break; } @@ -1561,6 +1586,8 @@ private void UpdateMenu() tslnudBlurRadius.Visible = shapeType == ShapeType.EffectBlur; tslnudPixelateSize.Visible = shapeType == ShapeType.EffectPixelate; tsbHighlightColor.Visible = shapeType == ShapeType.EffectHighlight; + tscbCutOutEffectType.Visible = shapeType == ShapeType.ToolCutOut; + tslnudCutOutEffectSize.Visible = shapeType == ShapeType.ToolCutOut; if (tsmiRegionCapture != null) { From 74a16fd317473fae03d74969b1b5e93fe0b9fc0e Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 21:48:07 +0200 Subject: [PATCH 17/23] Make cut out effect type names localized --- ShareX.HelpersLib/Enums.cs | 2 +- .../Properties/Resources.Designer.cs | 45 +++++++++++++++++++ ShareX.HelpersLib/Properties/Resources.resx | 16 ++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/ShareX.HelpersLib/Enums.cs b/ShareX.HelpersLib/Enums.cs index 4b7076fec..e32becc81 100644 --- a/ShareX.HelpersLib/Enums.cs +++ b/ShareX.HelpersLib/Enums.cs @@ -213,7 +213,7 @@ public enum StepType // Localized RomanNumeralsLowercase } - public enum CutOutEffectType // TODO: localize + public enum CutOutEffectType // Localized { None, ZigZag, diff --git a/ShareX.HelpersLib/Properties/Resources.Designer.cs b/ShareX.HelpersLib/Properties/Resources.Designer.cs index c8edd5934..d51dcc064 100644 --- a/ShareX.HelpersLib/Properties/Resources.Designer.cs +++ b/ShareX.HelpersLib/Properties/Resources.Designer.cs @@ -685,6 +685,42 @@ internal class Resources { } } + /// + /// Looks up a localized string similar to No effect. + /// + internal static string CutOutEffectType_None { + get { + return ResourceManager.GetString("CutOutEffectType_None", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Torn edges. + /// + internal static string CutOutEffectType_TornEdge { + get { + return ResourceManager.GetString("CutOutEffectType_TornEdge", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wave. + /// + internal static string CutOutEffectType_Wave { + get { + return ResourceManager.GetString("CutOutEffectType_Wave", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sawtooth. + /// + internal static string CutOutEffectType_ZigZag { + get { + return ResourceManager.GetString("CutOutEffectType_ZigZag", resourceCulture); + } + } + /// /// Looks up a localized string similar to Browse for a folder.... /// @@ -3652,6 +3688,15 @@ internal class Resources { } } + /// + /// Looks up a localized string similar to Cut out. + /// + internal static string ShapeType_ToolCutOut { + get { + return ResourceManager.GetString("ShapeType_ToolCutOut", resourceCulture); + } + } + /// /// Looks up a localized string similar to Select and move (M). /// diff --git a/ShareX.HelpersLib/Properties/Resources.resx b/ShareX.HelpersLib/Properties/Resources.resx index 3d5e7a523..0d1bb0f1e 100644 --- a/ShareX.HelpersLib/Properties/Resources.resx +++ b/ShareX.HelpersLib/Properties/Resources.resx @@ -1429,7 +1429,6 @@ Would you like to download and install it? OCR - Tools @@ -1455,4 +1454,19 @@ Would you like to download and install it? Tools + + No effect + + + Torn edges + + + Wave + + + Sawtooth + + + Cut out + \ No newline at end of file From 888db53d8318f4de390dc23c2f92fe6f67089095 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 22:45:46 +0200 Subject: [PATCH 18/23] Factor out the affected area display rectangle for CutOutTool visual --- .../Shapes/Tool/CutOutTool.cs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs index 76a6277c5..901b01d8c 100644 --- a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs @@ -49,6 +49,22 @@ public class CutOutTool : BaseTool } } + public RectangleF CutOutRectangle + { + get + { + if (IsHorizontalTrim) + { + return new RectangleF(Rectangle.X, Manager.Form.CanvasRectangle.Y, Rectangle.Width, Manager.Form.CanvasRectangle.Height); + } + if (IsVerticalTrim) + { + return new RectangleF(Manager.Form.CanvasRectangle.X, Rectangle.Y, Manager.Form.CanvasRectangle.Width, Rectangle.Height); + } + return RectangleF.Empty; + } + } + private ImageEditorButton confirmButton, cancelButton; private Size buttonSize = new Size(80, 40); private int buttonOffset = 15; @@ -121,14 +137,7 @@ public override void OnDraw(Graphics g) using (Image selectionHighlightPattern = ImageHelpers.CreateCheckerPattern(8, 8, Color.FromArgb(128, Color.White), Color.FromArgb(128, Color.Gray))) using (Brush selectionHighlightBrush = new TextureBrush(selectionHighlightPattern, System.Drawing.Drawing2D.WrapMode.Tile)) { - if (IsHorizontalTrim) - { - g.FillRectangle(selectionHighlightBrush, new RectangleF(Rectangle.X, Math.Max(g.ClipBounds.Y, Manager.Form.CanvasRectangle.Y), Rectangle.Width, Math.Min(g.ClipBounds.Height, Manager.Form.CanvasRectangle.Height))); - } - else if (IsVerticalTrim) - { - g.FillRectangle(selectionHighlightBrush, new RectangleF(Math.Max(g.ClipBounds.X, Manager.Form.CanvasRectangle.X), Rectangle.Y, Math.Min(g.ClipBounds.Width, Manager.Form.CanvasRectangle.Width), Rectangle.Height)); - } + g.FillRectangle(selectionHighlightBrush, CutOutRectangle); } } From bac400ebf8ab279fba5a695b24c9f464a52277cf Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 23:11:52 +0200 Subject: [PATCH 19/23] Add WaveEdge as image effect --- ShareX.ImageEffectsLib/Filters/WaveEdge.cs | 54 +++++++++++++++++++ .../Forms/ImageEffectsForm.cs | 3 +- .../ShareX.ImageEffectsLib.csproj | 1 + 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 ShareX.ImageEffectsLib/Filters/WaveEdge.cs diff --git a/ShareX.ImageEffectsLib/Filters/WaveEdge.cs b/ShareX.ImageEffectsLib/Filters/WaveEdge.cs new file mode 100644 index 000000000..58416ccfe --- /dev/null +++ b/ShareX.ImageEffectsLib/Filters/WaveEdge.cs @@ -0,0 +1,54 @@ +#region License Information (GPL v3) + +/* + ShareX - A program that allows you to take screenshots and share any file type + Copyright (c) 2007-2022 ShareX Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Optionally you can also view the license at . +*/ + +#endregion License Information (GPL v3) + +using ShareX.HelpersLib; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace ShareX.ImageEffectsLib +{ + [Description("Wave edge")] + internal class WaveEdge : ImageEffect + { + public int Depth { get; set; } + + [DefaultValue(10)] + public int Range { get; set; } + + [DefaultValue(AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right)] + public AnchorStyles Sides { get; set; } + + public WaveEdge() + { + this.ApplyDefaultPropertyValues(); + } + + public override Bitmap Apply(Bitmap bmp) + { + return ImageHelpers.WavyEdges(bmp, Depth, Range, Sides); + } + } +} diff --git a/ShareX.ImageEffectsLib/Forms/ImageEffectsForm.cs b/ShareX.ImageEffectsLib/Forms/ImageEffectsForm.cs index bddf9532d..ea10b0bb1 100644 --- a/ShareX.ImageEffectsLib/Forms/ImageEffectsForm.cs +++ b/ShareX.ImageEffectsLib/Forms/ImageEffectsForm.cs @@ -202,7 +202,8 @@ private void AddAllEffectsToContextMenu() typeof(Sharpen), typeof(Slice), typeof(Smooth), - typeof(TornEdge)); + typeof(TornEdge), + typeof(WaveEdge)); } private void AddEffectToContextMenu(string groupName, params Type[] imageEffects) diff --git a/ShareX.ImageEffectsLib/ShareX.ImageEffectsLib.csproj b/ShareX.ImageEffectsLib/ShareX.ImageEffectsLib.csproj index 6f0889a7b..bad5d59d8 100644 --- a/ShareX.ImageEffectsLib/ShareX.ImageEffectsLib.csproj +++ b/ShareX.ImageEffectsLib/ShareX.ImageEffectsLib.csproj @@ -136,6 +136,7 @@ + Form From 99f69676202c45fd72342282fa73e5cace868d1d Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 23:18:47 +0200 Subject: [PATCH 20/23] Bugfixes for WavyEdges function --- ShareX.HelpersLib/Helpers/ImageHelpers.cs | 41 ++++++++++++----------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs index 0ab2f3be9..5c8345444 100644 --- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs +++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs @@ -1673,6 +1673,8 @@ public static Bitmap WavyEdges(Bitmap bmp, int waveDepth, int waveRange, AnchorS int horizontalWaveCount = Math.Max(2, (bmp.Width / waveRange + 1) / 2 * 2) - 1; int verticalWaveCount = Math.Max(2, (bmp.Height / waveRange + 1) / 2 * 2) - 1; + int step = Math.Min(Math.Max(1, waveRange / waveDepth), 10); + Bitmap updateResult(Bitmap bmpIn, Point[] path) { Bitmap bmpResult = bmpIn.CreateEmptyBitmap(); @@ -1688,55 +1690,56 @@ Bitmap updateResult(Bitmap bmpIn, Point[] path) int waveFunction(int t, int max, int depth) => (int)((1 - Math.Cos(t * Math.PI / max)) * depth / 2); - if (sides.HasFlag(AnchorStyles.Top) && horizontalWaveCount > 1) + if (sides.HasFlag(AnchorStyles.Top)) { waveRange = bmp.Width / horizontalWaveCount; points.Clear(); - for (int x = 0; x < bmp.Width; x += waveDepth) + for (int x = 0; x < bmp.Width; x += step) { points.Add(new Point(x, waveFunction(x, waveRange, waveDepth))); } - points.Add(new Point(bmp.Width - 1, waveFunction(bmp.Width - 1, waveRange, waveDepth))); - points.Add(new Point(bmp.Width - 1, bmp.Height - 1)); - points.Add(new Point(0, bmp.Height - 1)); + points.Add(new Point(bmp.Width, waveFunction(bmp.Width, waveRange, waveDepth))); + points.Add(new Point(bmp.Width, bmp.Height)); + points.Add(new Point(0, bmp.Height)); bmp = updateResult(bmp, points.ToArray()); } - if (sides.HasFlag(AnchorStyles.Right) && verticalWaveCount > 1) + if (sides.HasFlag(AnchorStyles.Right)) { waveRange = bmp.Height / verticalWaveCount; points.Clear(); points.Add(new Point(0, 0)); - for (int y = 0; y < bmp.Height; y += waveDepth) + for (int y = 0; y < bmp.Height; y += step) { - points.Add(new Point(bmp.Width - 1 - waveDepth + waveFunction(y, waveRange, waveDepth), y)); + points.Add(new Point(bmp.Width - waveDepth + waveFunction(y, waveRange, waveDepth), y)); } - points.Add(new Point(bmp.Width - 1 - waveDepth + waveFunction(bmp.Height - 1, waveRange, waveDepth), bmp.Height - 1)); - points.Add(new Point(0, bmp.Height - 1)); + points.Add(new Point(bmp.Width - waveDepth + waveFunction(bmp.Height, waveRange, waveDepth), bmp.Height)); + points.Add(new Point(0, bmp.Height)); bmp = updateResult(bmp, points.ToArray()); } - if (sides.HasFlag(AnchorStyles.Bottom) && horizontalWaveCount > 1) + if (sides.HasFlag(AnchorStyles.Bottom)) { waveRange = bmp.Width / horizontalWaveCount; points.Clear(); points.Add(new Point(0, 0)); - points.Add(new Point(bmp.Width - 1, 0)); - for (int x = bmp.Width - 1; x >= 0; x -= waveDepth) + points.Add(new Point(bmp.Width, 0)); + for (int x = bmp.Width; x >= 0; x -= step) { - points.Add(new Point(x, bmp.Height - 1 - waveDepth + waveFunction(x, waveRange, waveDepth))); + points.Add(new Point(x, bmp.Height - waveDepth + waveFunction(x, waveRange, waveDepth))); } - points.Add(new Point(0, bmp.Height - 1 - waveDepth + waveFunction(0, waveRange, waveDepth))); + points.Add(new Point(0, bmp.Height - waveDepth + waveFunction(0, waveRange, waveDepth))); bmp = updateResult(bmp, points.ToArray()); } - if (sides.HasFlag(AnchorStyles.Left) && verticalWaveCount > 1) + if (sides.HasFlag(AnchorStyles.Left)) { waveRange = bmp.Height / verticalWaveCount; + points.Clear(); points.Add(new Point(0, 0)); - points.Add(new Point(bmp.Width - 1, 0)); - points.Add(new Point(bmp.Width - 1, bmp.Height - 1)); - for (int y = bmp.Height - 1; y >= 0; y -= waveDepth) + points.Add(new Point(bmp.Width, 0)); + points.Add(new Point(bmp.Width, bmp.Height)); + for (int y = bmp.Height; y >= 0; y -= step) { points.Add(new Point(waveFunction(y, waveRange, waveDepth), y)); } From a37116a0dcebabb2fdf8efd227cf5c091675c619 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Wed, 17 Aug 2022 23:42:49 +0200 Subject: [PATCH 21/23] Code cleanup --- ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs index 901b01d8c..1ccb8201a 100644 --- a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs @@ -24,7 +24,6 @@ #endregion License Information (GPL v3) using ShareX.HelpersLib; -using System; using System.Drawing; using System.Windows.Forms; @@ -39,7 +38,8 @@ public class CutOutTool : BaseTool public bool IsHorizontalTrim => Rectangle.Width >= Options.MinimumSize && Rectangle.Width > Rectangle.Height; public bool IsVerticalTrim => Rectangle.Height >= Options.MinimumSize && Rectangle.Height >= Rectangle.Width; - public override bool IsValidShape { + public override bool IsValidShape + { get { if (!IsHorizontalTrim && !IsVerticalTrim) return false; From 838a6329998ce44e03f8939ea8a4c1b0949a1a4b Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Thu, 18 Aug 2022 10:18:04 +0200 Subject: [PATCH 22/23] Correct WavyEdges and TornEdges effects for pixel center origin --- ShareX.HelpersLib/Helpers/ImageHelpers.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs index 5c8345444..fe188ac5b 100644 --- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs +++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs @@ -1683,6 +1683,7 @@ Bitmap updateResult(Bitmap bmpIn, Point[] path) using (TextureBrush brush = new TextureBrush(bmpIn)) { g.SetHighQuality(); + g.PixelOffsetMode = PixelOffsetMode.Half; g.FillPolygon(brush, path); } return bmpResult; @@ -1829,6 +1830,7 @@ public static Bitmap TornEdges(Bitmap bmp, int tornDepth, int tornRange, AnchorS using (TextureBrush brush = new TextureBrush(bmp)) { g.SetHighQuality(); + g.PixelOffsetMode = PixelOffsetMode.Half; Point[] fillPoints = points.Distinct().ToArray(); From 2e23856e924617f26df35b620a955c4547e7a656 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Thu, 18 Aug 2022 11:27:58 +0200 Subject: [PATCH 23/23] Use a 1x1 checkerboard fill pattern for selection highlight --- ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs index 1ccb8201a..52b5dc4cb 100644 --- a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs @@ -134,7 +134,7 @@ public override void OnUpdate() public override void OnDraw(Graphics g) { - using (Image selectionHighlightPattern = ImageHelpers.CreateCheckerPattern(8, 8, Color.FromArgb(128, Color.White), Color.FromArgb(128, Color.Gray))) + using (Image selectionHighlightPattern = ImageHelpers.CreateCheckerPattern(1, 1, Color.FromArgb(128, Color.LightGray), Color.FromArgb(128, Color.Gray))) using (Brush selectionHighlightBrush = new TextureBrush(selectionHighlightPattern, System.Drawing.Drawing2D.WrapMode.Tile)) { g.FillRectangle(selectionHighlightBrush, CutOutRectangle);