mirror of
https://github.com/ShareX/ShareX.git
synced 2024-09-30 01:07:21 +13:00
commit
5ece804ace
16 changed files with 703 additions and 21 deletions
|
@ -212,4 +212,12 @@ public enum StepType // Localized
|
|||
RomanNumeralsUppercase,
|
||||
RomanNumeralsLowercase
|
||||
}
|
||||
|
||||
public enum CutOutEffectType // Localized
|
||||
{
|
||||
None,
|
||||
ZigZag,
|
||||
TornEdge,
|
||||
Wave
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
45
ShareX.HelpersLib/Properties/Resources.Designer.cs
generated
45
ShareX.HelpersLib/Properties/Resources.Designer.cs
generated
|
@ -685,6 +685,42 @@ internal static string CustomUploaderDestinationType_URLShortener {
|
|||
}
|
||||
}
|
||||
|
||||
/// <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 static string ShapeType_ToolCrop {
|
|||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
54
ShareX.ImageEffectsLib/Filters/WaveEdge.cs
Normal file
54
ShareX.ImageEffectsLib/Filters/WaveEdge.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -291,7 +291,8 @@ public enum ShapeType // Localized
|
|||
EffectBlur,
|
||||
EffectPixelate,
|
||||
EffectHighlight,
|
||||
ToolCrop
|
||||
ToolCrop,
|
||||
ToolCutOut
|
||||
}
|
||||
|
||||
public enum ScrollingCaptureScrollMethod // Localized
|
||||
|
|
|
@ -284,6 +284,24 @@ internal static System.Drawing.Bitmap cursor {
|
|||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
212
ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs
Normal file
212
ShareX.ScreenCaptureLib/Shapes/Tool/CutOutTool.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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">
|
||||
|
|
Loading…
Reference in a new issue