diff --git a/ShareX.HelpersLib/ConvolutionMatrix.cs b/ShareX.HelpersLib/ConvolutionMatrix.cs index 21e7c80a9..4049c7d8f 100644 --- a/ShareX.HelpersLib/ConvolutionMatrix.cs +++ b/ShareX.HelpersLib/ConvolutionMatrix.cs @@ -27,31 +27,36 @@ namespace ShareX.HelpersLib { public class ConvolutionMatrix { - public int Size { get; private set; } - public int[,] Matrix { get; set; } - public int Factor { get; set; } - public int Offset { get; set; } + private readonly double[,] matrix; + + public int Width => matrix.GetLength(1); + public int Height => matrix.GetLength(0); + public byte Offset { get; set; } public ConvolutionMatrix() : this(3) { } - public ConvolutionMatrix(int size) + public ConvolutionMatrix(int size) : this(size, size) { - Size = size; - Matrix = new int[Size, Size]; - Factor = 1; } - public void SetAll(int value) + public ConvolutionMatrix(int height, int width) { - for (int y = 0; y < Size; y++) + matrix = new double[height, width]; + } + + public void SetAll(double value) + { + for (int y = 0; y < Height; y++) { - for (int x = 0; x < Size; x++) + for (int x = 0; x < Width; x++) { - Matrix[x, y] = value; + matrix[y, x] = value; } } } + + public ref double this[int y, int x] => ref matrix[y, x]; } } \ No newline at end of file diff --git a/ShareX.HelpersLib/ConvolutionMatrixManager.cs b/ShareX.HelpersLib/ConvolutionMatrixManager.cs index d9bd522b0..ce91d17db 100644 --- a/ShareX.HelpersLib/ConvolutionMatrixManager.cs +++ b/ShareX.HelpersLib/ConvolutionMatrixManager.cs @@ -23,6 +23,7 @@ You should have received a copy of the GNU General Public License #endregion License Information (GPL v3) +// Adapted from https://stackoverflow.com/questions/33569396/correctly-implement-a-2-pass-gaussian-blur // Filters: http://www.codeproject.com/Articles/2008/Image-Processing-for-Dummies-with-C-and-GDI-Part-2 using System; @@ -33,83 +34,57 @@ namespace ShareX.HelpersLib { public static class ConvolutionMatrixManager { - public static Image Apply(this ConvolutionMatrix matrix, Image img) + public static Image Apply(this ConvolutionMatrix kernel, Image img) { - int factor = Math.Max(matrix.Factor, 1); - Bitmap result = (Bitmap)img.Clone(); using (UnsafeBitmap source = new UnsafeBitmap((Bitmap)img, true, ImageLockMode.ReadOnly)) using (UnsafeBitmap dest = new UnsafeBitmap(result, true, ImageLockMode.WriteOnly)) { - int height = source.Height - 2; - int width = source.Width - 2; - ColorBgra[,] pixelColor = new ColorBgra[3, 3]; - int pixel; - ColorBgra color = new ColorBgra(); + int originX = (kernel.Width - 1) / 2; + int originY = (kernel.Height - 1) / 2; - for (int y = 0; y < height; y++) + for (int y = 0; y < source.Height; y++) { - for (int x = 0; x < width; x++) + for (int x = 0; x < source.Width; x++) { - pixelColor[0, 0] = source.GetPixel(x, y); - pixelColor[0, 1] = source.GetPixel(x, y + 1); - pixelColor[0, 2] = source.GetPixel(x, y + 2); - pixelColor[1, 0] = source.GetPixel(x + 1, y); - pixelColor[1, 1] = source.GetPixel(x + 1, y + 1); - pixelColor[1, 2] = source.GetPixel(x + 1, y + 2); - pixelColor[2, 0] = source.GetPixel(x + 2, y); - pixelColor[2, 1] = source.GetPixel(x + 2, y + 1); - pixelColor[2, 2] = source.GetPixel(x + 2, y + 2); + double r = 0.0; + double g = 0.0; + double b = 0.0; - pixel = (((pixelColor[0, 0].Blue * matrix.Matrix[0, 0]) + - (pixelColor[1, 0].Blue * matrix.Matrix[1, 0]) + - (pixelColor[2, 0].Blue * matrix.Matrix[2, 0]) + - (pixelColor[0, 1].Blue * matrix.Matrix[0, 1]) + - (pixelColor[1, 1].Blue * matrix.Matrix[1, 1]) + - (pixelColor[2, 1].Blue * matrix.Matrix[2, 1]) + - (pixelColor[0, 2].Blue * matrix.Matrix[0, 2]) + - (pixelColor[1, 2].Blue * matrix.Matrix[1, 2]) + - (pixelColor[2, 2].Blue * matrix.Matrix[2, 2])) / factor) + matrix.Offset; + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernel.Height; fy++) + { + int fyr = fy - originY; + int offsetY = y + fyr; - if (pixel < 0) pixel = 0; - else if (pixel > 255) pixel = 255; + offsetY.Clamp(0, source.Height - 1); - color.Blue = (byte)pixel; + for (int fx = 0; fx < kernel.Width; fx++) + { + int fxr = fx - originX; + int offsetX = x + fxr; - pixel = (((pixelColor[0, 0].Green * matrix.Matrix[0, 0]) + - (pixelColor[1, 0].Green * matrix.Matrix[1, 0]) + - (pixelColor[2, 0].Green * matrix.Matrix[2, 0]) + - (pixelColor[0, 1].Green * matrix.Matrix[0, 1]) + - (pixelColor[1, 1].Green * matrix.Matrix[1, 1]) + - (pixelColor[2, 1].Green * matrix.Matrix[2, 1]) + - (pixelColor[0, 2].Green * matrix.Matrix[0, 2]) + - (pixelColor[1, 2].Green * matrix.Matrix[1, 2]) + - (pixelColor[2, 2].Green * matrix.Matrix[2, 2])) / factor) + matrix.Offset; + offsetX.Clamp(0, source.Width - 1); - if (pixel < 0) pixel = 0; - else if (pixel > 255) pixel = 255; + ColorBgra currentColor = source.GetPixel(offsetX, offsetY); - color.Green = (byte)pixel; + r += kernel[fy, fx] * currentColor.Red; + g += kernel[fy, fx] * currentColor.Green; + b += kernel[fy, fx] * currentColor.Blue; + } + } - pixel = (((pixelColor[0, 0].Red * matrix.Matrix[0, 0]) + - (pixelColor[1, 0].Red * matrix.Matrix[1, 0]) + - (pixelColor[2, 0].Red * matrix.Matrix[2, 0]) + - (pixelColor[0, 1].Red * matrix.Matrix[0, 1]) + - (pixelColor[1, 1].Red * matrix.Matrix[1, 1]) + - (pixelColor[2, 1].Red * matrix.Matrix[2, 1]) + - (pixelColor[0, 2].Red * matrix.Matrix[0, 2]) + - (pixelColor[1, 2].Red * matrix.Matrix[1, 2]) + - (pixelColor[2, 2].Red * matrix.Matrix[2, 2])) / factor) + matrix.Offset; + r += kernel.Offset; + r.Clamp(0, 255); - if (pixel < 0) pixel = 0; - else if (pixel > 255) pixel = 255; + g += kernel.Offset; + g.Clamp(0, 255); - color.Red = (byte)pixel; + b += kernel.Offset; + b.Clamp(0, 255); - color.Alpha = pixelColor[1, 1].Alpha; - - dest.SetPixel(x + 1, y + 1, color); + dest.SetPixel(x, y, new ColorBgra((byte)b, (byte)g, (byte)r, source.GetPixel(x, y).Alpha)); } } } @@ -120,38 +95,67 @@ public static Image Apply(this ConvolutionMatrix matrix, Image img) public static ConvolutionMatrix Smooth(int weight = 1) { ConvolutionMatrix cm = new ConvolutionMatrix(); - cm.SetAll(1); - cm.Matrix[1, 1] = weight; - cm.Factor = weight + 8; + double factor = weight + 8; + cm.SetAll(1 / factor); + cm[1, 1] = weight / factor; return cm; } - public static ConvolutionMatrix GaussianBlur(int weight = 4) + private static double GaussianFunction(double x, double sigma) { - ConvolutionMatrix cm = new ConvolutionMatrix(); - cm.SetAll(1); - cm.Matrix[1, 1] = weight; - cm.Matrix[1, 0] = cm.Matrix[0, 1] = cm.Matrix[2, 1] = cm.Matrix[1, 2] = 2; - cm.Factor = weight + 12; + double left = 1.0 / (Math.Sqrt(2 * Math.PI) * sigma); + + double exponentNumerator = -x * x; + double exponentDenominator = 2 * Math.Pow(sigma, 2); + double right = Math.Exp(exponentNumerator / exponentDenominator); + + return left * right; + } + + public static ConvolutionMatrix GaussianBlur(int height, int width, double sigma) + { + ConvolutionMatrix cm = new ConvolutionMatrix(height, width); + + double sum = 0.0; + double midpointX = (width - 1) / 2.0; + double midpointY = (height - 1) / 2.0; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + sum += cm[y, x] = GaussianFunction(x - midpointX, sigma) * GaussianFunction(y - midpointY, sigma); + } + } + + // Normalise kernel so that the sum of all weights equals 1 + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + cm[y, x] /= sum; + } + } + return cm; } public static ConvolutionMatrix MeanRemoval(int weight = 9) { ConvolutionMatrix cm = new ConvolutionMatrix(); - cm.SetAll(-1); - cm.Matrix[1, 1] = weight; - cm.Factor = weight - 8; + double factor = weight - 8; + cm.SetAll(-1 / factor); + cm[1, 1] = weight / factor; return cm; } public static ConvolutionMatrix Sharpen(int weight = 11) { ConvolutionMatrix cm = new ConvolutionMatrix(); + double factor = weight - 8; cm.SetAll(0); - cm.Matrix[1, 1] = weight; - cm.Matrix[1, 0] = cm.Matrix[0, 1] = cm.Matrix[2, 1] = cm.Matrix[1, 2] = -2; - cm.Factor = weight - 8; + cm[1, 1] = weight / factor; + cm[1, 0] = cm[0, 1] = cm[2, 1] = cm[1, 2] = -2 / factor; return cm; } @@ -159,8 +163,8 @@ public static ConvolutionMatrix Emboss() { ConvolutionMatrix cm = new ConvolutionMatrix(); cm.SetAll(-1); - cm.Matrix[1, 1] = 4; - cm.Matrix[1, 0] = cm.Matrix[0, 1] = cm.Matrix[2, 1] = cm.Matrix[1, 2] = 0; + cm[1, 1] = 4; + cm[1, 0] = cm[0, 1] = cm[2, 1] = cm[1, 2] = 0; cm.Offset = 127; return cm; } @@ -168,9 +172,9 @@ public static ConvolutionMatrix Emboss() public static ConvolutionMatrix EdgeDetect() { ConvolutionMatrix cm = new ConvolutionMatrix(); - cm.Matrix[0, 0] = cm.Matrix[1, 0] = cm.Matrix[2, 0] = -1; - cm.Matrix[0, 1] = cm.Matrix[1, 1] = cm.Matrix[2, 1] = 0; - cm.Matrix[0, 2] = cm.Matrix[1, 2] = cm.Matrix[2, 2] = 1; + cm[0, 0] = cm[0, 1] = cm[0, 2] = -1; + cm[1, 0] = cm[1, 1] = cm[1, 2] = 0; + cm[2, 0] = cm[2, 1] = cm[2, 2] = 1; cm.Offset = 127; return cm; } diff --git a/ShareX.HelpersLib/Extensions/NumberExtensions.cs b/ShareX.HelpersLib/Extensions/NumberExtensions.cs index ad25dd008..dd9326209 100644 --- a/ShareX.HelpersLib/Extensions/NumberExtensions.cs +++ b/ShareX.HelpersLib/Extensions/NumberExtensions.cs @@ -149,5 +149,10 @@ public static bool IsEvenNumber(this int num) { return num % 2 == 0; } + + public static void Clamp(ref this T val, T min, T max) where T : struct, IComparable + { + MathHelpers.Clamp(ref val, min, max); + } } } \ No newline at end of file diff --git a/ShareX.HelpersLib/Helpers/MathHelpers.cs b/ShareX.HelpersLib/Helpers/MathHelpers.cs index c55879d7f..d6fa72a01 100644 --- a/ShareX.HelpersLib/Helpers/MathHelpers.cs +++ b/ShareX.HelpersLib/Helpers/MathHelpers.cs @@ -185,5 +185,23 @@ public static Vector2 Lerp(Vector2 pos1, Vector2 pos2, float amount) float y = Lerp(pos1.Y, pos2.Y, amount); return new Vector2(x, y); } + + public static void Clamp(ref T val, T min, T max) where T : IComparable + { + if (val.CompareTo(min) < 0) + { + val = min; + } + else if (val.CompareTo(max) > 0) + { + val = max; + } + } + + public static T Clamp(T val, T min, T max) where T : IComparable + { + Clamp(ref val, min, max); + return val; + } } } \ No newline at end of file diff --git a/ShareX.HelpersLib/ShareX.HelpersLib.csproj b/ShareX.HelpersLib/ShareX.HelpersLib.csproj index 8be6cb052..e46d45003 100644 --- a/ShareX.HelpersLib/ShareX.HelpersLib.csproj +++ b/ShareX.HelpersLib/ShareX.HelpersLib.csproj @@ -33,6 +33,7 @@ Off false true + 7.2 none @@ -48,6 +49,7 @@ true Off false + 7.2 bin\Steam\ @@ -59,6 +61,7 @@ prompt MinimumRecommendedRules.ruleset false + 7.2 bin\WindowsStore\ @@ -70,6 +73,7 @@ prompt MinimumRecommendedRules.ruleset false + 7.2 true @@ -82,6 +86,7 @@ prompt MinimumRecommendedRules.ruleset false + 7.2 diff --git a/ShareX.ImageEffectsLib/Filters/GaussianBlur.cs b/ShareX.ImageEffectsLib/Filters/GaussianBlur.cs index fe4fb4e4b..16ba7db14 100644 --- a/ShareX.ImageEffectsLib/Filters/GaussianBlur.cs +++ b/ShareX.ImageEffectsLib/Filters/GaussianBlur.cs @@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License #endregion License Information (GPL v3) using ShareX.HelpersLib; +using System; using System.ComponentModel; using System.Drawing; @@ -32,11 +33,50 @@ namespace ShareX.ImageEffectsLib [Description("Gaussian blur")] internal class GaussianBlur : ImageEffect { + private double sigma; + private int size; + + [DefaultValue(0.7955555)] + public double Sigma + { + get => sigma; + set => sigma = Math.Max(value, 0.1); + } + + [DefaultValue(3)] + public int Size + { + get => size; + set + { + size = value.Min(1); + + if (size.IsEvenNumber()) + { + size++; + } + } + } + + public GaussianBlur() + { + this.ApplyDefaultPropertyValues(); + } + public override Image Apply(Image img) { - using (img) + ConvolutionMatrix kernelHoriz = ConvolutionMatrixManager.GaussianBlur(1, size, sigma); + + ConvolutionMatrix kernelVert = new ConvolutionMatrix(size, 1); + for (int i = 0; i < size; i++) { - return ConvolutionMatrixManager.GaussianBlur().Apply(img); + kernelVert[i, 0] = kernelHoriz[0, i]; + } + + using (img) + using (Image horizPass = kernelHoriz.Apply(img)) + { + return kernelVert.Apply(horizPass); } } } diff --git a/ShareX.ImageEffectsLib/Filters/MatrixConvolution.cs b/ShareX.ImageEffectsLib/Filters/MatrixConvolution.cs index d14766242..0dbcab1b0 100644 --- a/ShareX.ImageEffectsLib/Filters/MatrixConvolution.cs +++ b/ShareX.ImageEffectsLib/Filters/MatrixConvolution.cs @@ -53,11 +53,11 @@ internal class MatrixConvolution : ImageEffect [DefaultValue(0)] public int X2Y2 { get; set; } - [DefaultValue(1)] - public int Factor { get; set; } + [DefaultValue(1.0)] + public double Factor { get; set; } [DefaultValue(0)] - public int Offset { get; set; } + public byte Offset { get; set; } public MatrixConvolution() { @@ -69,16 +69,15 @@ public override Image Apply(Image img) using (img) { ConvolutionMatrix cm = new ConvolutionMatrix(); - cm.Matrix[0, 0] = X0Y0; - cm.Matrix[1, 0] = X1Y0; - cm.Matrix[2, 0] = X2Y0; - cm.Matrix[0, 1] = X0Y1; - cm.Matrix[1, 1] = X1Y1; - cm.Matrix[2, 1] = X2Y1; - cm.Matrix[0, 2] = X0Y2; - cm.Matrix[1, 2] = X1Y2; - cm.Matrix[2, 2] = X2Y2; - cm.Factor = Factor; + cm[0, 0] = X0Y0 / Factor; + cm[0, 1] = X1Y0 / Factor; + cm[0, 2] = X2Y0 / Factor; + cm[1, 0] = X0Y1 / Factor; + cm[1, 1] = X1Y1 / Factor; + cm[1, 2] = X2Y1 / Factor; + cm[2, 0] = X0Y2 / Factor; + cm[2, 1] = X1Y2 / Factor; + cm[2, 2] = X2Y2 / Factor; cm.Offset = Offset; return cm.Apply(img); }