From b1f7441176411d9404076e03da702ccb393fc30a Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Tue, 16 Aug 2022 00:22:54 +0200 Subject: [PATCH] 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 @@ +