diff --git a/ShareX.HelpersLib/Enums.cs b/ShareX.HelpersLib/Enums.cs index 59fe208ab..e32becc81 100644 --- a/ShareX.HelpersLib/Enums.cs +++ b/ShareX.HelpersLib/Enums.cs @@ -212,4 +212,12 @@ public enum StepType // Localized RomanNumeralsUppercase, RomanNumeralsLowercase } + + public enum CutOutEffectType // Localized + { + None, + ZigZag, + TornEdge, + Wave + } } \ No newline at end of file diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs index fe8454afb..fe188ac5b 100644 --- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs +++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs @@ -223,6 +223,71 @@ public static Bitmap CropBitmap(Bitmap bmp, Rectangle rect) return null; } + private static Bitmap ApplyCutOutEffect(Bitmap bmp, AnchorStyles effectEdge, CutOutEffectType effectType, int effectSize) + { + switch (effectType) + { + case CutOutEffectType.None: + return bmp; + + case CutOutEffectType.ZigZag: + return TornEdges(bmp, effectSize, effectSize, effectEdge, false, false); + + case CutOutEffectType.TornEdge: + return TornEdges(bmp, effectSize, effectSize * 2, effectEdge, false, true); + + case CutOutEffectType.Wave: + return WavyEdges(bmp, effectSize, effectSize * 5, effectEdge); + } + + throw new NotImplementedException(); // should not be reachable + } + + public static Bitmap CutOutBitmapMiddle(Bitmap bmp, Orientation orientation, int start, int size, CutOutEffectType effectType, int effectSize) + { + if (bmp != null && size > 0) + { + Bitmap firstPart = null, secondPart = null; + + if (start > 0) + { + 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); + } + + int cutDimension = orientation == Orientation.Horizontal ? bmp.Width : bmp.Height; + if (start + size < cutDimension) + { + 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); + } + + if (firstPart != null && secondPart != null) + { + return CombineImages(new List { firstPart, secondPart }, orientation); + } + else if (firstPart != null) + { + return firstPart; + } + else if (secondPart != null) + { + return secondPart; + } + } + + return bmp; + } + /// Automatically crop image to remove transparent outside area. public static Bitmap AutoCropTransparent(Bitmap bmp) { @@ -1596,7 +1661,96 @@ public static void FastBoxBlur(Bitmap bmp, int radius) } } - public static Bitmap TornEdges(Bitmap bmp, int tornDepth, int tornRange, AnchorStyles sides, bool curvedEdges) + 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; + + int step = Math.Min(Math.Max(1, waveRange / waveDepth), 10); + + 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.PixelOffsetMode = PixelOffsetMode.Half; + 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)) + { + waveRange = bmp.Width / horizontalWaveCount; + points.Clear(); + for (int x = 0; x < bmp.Width; x += step) + { + points.Add(new Point(x, waveFunction(x, waveRange, waveDepth))); + } + 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)) + { + waveRange = bmp.Height / verticalWaveCount; + points.Clear(); + points.Add(new Point(0, 0)); + for (int y = 0; y < bmp.Height; y += step) + { + points.Add(new Point(bmp.Width - waveDepth + waveFunction(y, waveRange, waveDepth), y)); + } + 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)) + { + waveRange = bmp.Width / horizontalWaveCount; + points.Clear(); + points.Add(new Point(0, 0)); + points.Add(new Point(bmp.Width, 0)); + for (int x = bmp.Width; x >= 0; x -= step) + { + points.Add(new Point(x, bmp.Height - waveDepth + waveFunction(x, waveRange, waveDepth))); + } + points.Add(new Point(0, bmp.Height - waveDepth + waveFunction(0, waveRange, waveDepth))); + bmp = updateResult(bmp, points.ToArray()); + } + + if (sides.HasFlag(AnchorStyles.Left)) + { + waveRange = bmp.Height / verticalWaveCount; + points.Clear(); + points.Add(new Point(0, 0)); + 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)); + } + bmp = updateResult(bmp, points.ToArray()); + } + + return bmp; + } + + public static Bitmap TornEdges(Bitmap bmp, int tornDepth, int tornRange, AnchorStyles sides, bool curvedEdges, bool random) { if (tornDepth < 1 || tornRange < 1 || sides == AnchorStyles.None) { @@ -1615,53 +1769,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)); } @@ -1672,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(); 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 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.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 diff --git a/ShareX.ScreenCaptureLib/Enums.cs b/ShareX.ScreenCaptureLib/Enums.cs index c576902f1..710295771 100644 --- a/ShareX.ScreenCaptureLib/Enums.cs +++ b/ShareX.ScreenCaptureLib/Enums.cs @@ -291,7 +291,8 @@ public enum ShapeType // Localized EffectBlur, EffectPixelate, EffectHighlight, - ToolCrop + ToolCrop, + ToolCutOut } public enum ScrollingCaptureScrollMethod // Localized 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 565fda74f..297c5261b 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.ToolCutOut: + shape = new CutOutTool(); + break; } shape.Manager = this; @@ -1620,6 +1623,112 @@ public void MoveAll(PointF offset) MoveAll(offset.X, offset.Y); } + public void CollapseAllHorizontal(float x, float width) + { + 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) + { + DeleteShape(shape); + } + } + + public void CollapseAllVertical(float y, float height) + { + 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) + { + DeleteShape(shape); + } + } + public void RemoveOutsideShapes() { foreach (BaseShape shape in Shapes.ToArray()) @@ -1807,6 +1916,28 @@ public Bitmap CropImage(RectangleF rect, bool onlyIfSizeDifferent = false) return null; } + public void CutOut(RectangleF rect) + { + bool isHorizontal = rect.Width > rect.Height; + + RectangleF adjustedRect = CaptureHelpers.ScreenToClient(rect.Round()); + PointF offset = CaptureHelpers.ScreenToClient(Form.CanvasRectangle.Location.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) + { + CollapseAllHorizontal(rect.X, rect.Width); + 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, AnnotationOptions.CutOutEffectType, AnnotationOptions.CutOutEffectSize)); + } + } + public Color GetColor(Bitmap bmp, Point pos) { if (bmp != null) 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) { diff --git a/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs new file mode 100644 index 000000000..52b5dc4cb --- /dev/null +++ b/ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs @@ -0,0 +1,212 @@ +#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 CutOutTool : BaseTool + { + public override ShapeType ShapeType { get; } = ShapeType.ToolCutOut; + + 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 + { + 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; + } + } + + 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; + + public override void ShowNodes() + { + } + + public override void OnUpdate() + { + base.OnUpdate(); + + if (confirmButton != null && cancelButton != null) + { + if (IsVerticalTrim) + { + 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 + { + 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); + } + } + } + + public override void OnDraw(Graphics g) + { + 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); + } + } + + 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.CutOut(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..26ef6e832 100644 --- a/ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj +++ b/ShareX.ScreenCaptureLib/ShareX.ScreenCaptureLib.csproj @@ -226,6 +226,7 @@ +