Merge pull request #6454 from nielsmh/interior-trim

Add Cut Out tool
This commit is contained in:
Jaex 2022-08-18 16:30:10 +03:00 committed by GitHub
commit 5ece804ace
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 703 additions and 21 deletions

View file

@ -212,4 +212,12 @@ public enum StepType // Localized
RomanNumeralsUppercase,
RomanNumeralsLowercase
}
public enum CutOutEffectType // Localized
{
None,
ZigZag,
TornEdge,
Wave
}
}

View file

@ -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<Bitmap> { firstPart, secondPart }, orientation);
}
else if (firstPart != null)
{
return firstPart;
}
else if (secondPart != null)
{
return secondPart;
}
}
return bmp;
}
/// <summary>Automatically crop image to remove transparent outside area.</summary>
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<Point> points = new List<Point>();
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();

View file

@ -685,6 +685,42 @@ internal class Resources {
}
}
/// <summary>
/// Looks up a localized string similar to No effect.
/// </summary>
internal static string CutOutEffectType_None {
get {
return ResourceManager.GetString("CutOutEffectType_None", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Torn edges.
/// </summary>
internal static string CutOutEffectType_TornEdge {
get {
return ResourceManager.GetString("CutOutEffectType_TornEdge", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Wave.
/// </summary>
internal static string CutOutEffectType_Wave {
get {
return ResourceManager.GetString("CutOutEffectType_Wave", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Sawtooth.
/// </summary>
internal static string CutOutEffectType_ZigZag {
get {
return ResourceManager.GetString("CutOutEffectType_ZigZag", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Browse for a folder....
/// </summary>
@ -3652,6 +3688,15 @@ internal class Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Cut out.
/// </summary>
internal static string ShapeType_ToolCutOut {
get {
return ResourceManager.GetString("ShapeType_ToolCutOut", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select and move (M).
/// </summary>

View file

@ -1429,7 +1429,6 @@ Would you like to download and install it?</value>
</data>
<data name="HotkeyType_OCR" xml:space="preserve">
<value>OCR</value>
</data>
<data name="HotkeyType_OCR_Category" xml:space="preserve">
<value>Tools</value>
@ -1455,4 +1454,19 @@ Would you like to download and install it?</value>
<data name="HotkeyType_PinToScreen_Category" xml:space="preserve">
<value>Tools</value>
</data>
<data name="CutOutEffectType_None" xml:space="preserve">
<value>No effect</value>
</data>
<data name="CutOutEffectType_TornEdge" xml:space="preserve">
<value>Torn edges</value>
</data>
<data name="CutOutEffectType_Wave" xml:space="preserve">
<value>Wave</value>
</data>
<data name="CutOutEffectType_ZigZag" xml:space="preserve">
<value>Sawtooth</value>
</data>
<data name="ShapeType_ToolCutOut" xml:space="preserve">
<value>Cut out</value>
</data>
</root>

View file

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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}
}
}

View file

@ -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)

View file

@ -136,6 +136,7 @@
<Compile Include="Adjustments\Hue.cs" />
<Compile Include="Adjustments\Inverse.cs" />
<Compile Include="Adjustments\Saturation.cs" />
<Compile Include="Filters\WaveEdge.cs" />
<Compile Include="ImageEffectPackager.cs" />
<Compile Include="Forms\ImageEffectPackagerForm.cs">
<SubType>Form</SubType>

View file

@ -291,7 +291,8 @@ public enum ShapeType // Localized
EffectBlur,
EffectPixelate,
EffectHighlight,
ToolCrop
ToolCrop,
ToolCutOut
}
public enum ScrollingCaptureScrollMethod // Localized

View file

@ -284,6 +284,24 @@ internal class Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Cut cut effect size.
/// </summary>
internal static string CutOutEffectSize {
get {
return ResourceManager.GetString("CutOutEffectSize", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cut out effect.
/// </summary>
internal static string CutOutEffectType {
get {
return ResourceManager.GetString("CutOutEffectType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>

View file

@ -805,4 +805,10 @@ X: {4} Y: {5}</value>
<value>CRF:</value>
<comment>@Invariant</comment>
</data>
<data name="CutOutEffectSize" xml:space="preserve">
<value>Cut cut effect size</value>
</data>
<data name="CutOutEffectType" xml:space="preserve">
<value>Cut out effect</value>
</data>
</root>

View file

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

View file

@ -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<BaseShape> toDelete = new List<BaseShape>();
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<BaseShape> toDelete = new List<BaseShape>();
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)

View file

@ -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<CutOutEffectType>());
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)
{

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}
}
}
}

View file

@ -226,6 +226,7 @@
<Compile Include="Animations\TextAnimation.cs" />
<Compile Include="RegionHelpers\WindowsList.cs" />
<Compile Include="RegionHelpers\WindowsRectangleList.cs" />
<Compile Include="Shapes\Tool\CutOutTool.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ShareX.HelpersLib\ShareX.HelpersLib.csproj">