Merge pull request #3826 from sylveon/master

Add user customizable gaussian blur
This commit is contained in:
Jaex 2018-12-13 02:58:40 +03:00 committed by GitHub
commit d544515559
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 181 additions and 105 deletions

View file

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

View file

@ -23,6 +23,7 @@
#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;
}

View file

@ -149,5 +149,10 @@ public static bool IsEvenNumber(this int num)
{
return num % 2 == 0;
}
public static void Clamp<T>(ref this T val, T min, T max) where T : struct, IComparable<T>
{
MathHelpers.Clamp(ref val, min, max);
}
}
}

View file

@ -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<T>(ref T val, T min, T max) where T : IComparable<T>
{
if (val.CompareTo(min) < 0)
{
val = min;
}
else if (val.CompareTo(max) > 0)
{
val = max;
}
}
public static T Clamp<T>(T val, T min, T max) where T : IComparable<T>
{
Clamp(ref val, min, max);
return val;
}
}
}

View file

@ -33,6 +33,7 @@
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<Prefer32Bit>false</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
@ -48,6 +49,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<DebugSymbols>false</DebugSymbols>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Steam|AnyCPU'">
<OutputPath>bin\Steam\</OutputPath>
@ -59,6 +61,7 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'WindowsStore|AnyCPU'">
<OutputPath>bin\WindowsStore\</OutputPath>
@ -70,6 +73,7 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'WindowsStoreDebug|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
@ -82,6 +86,7 @@
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View file

@ -24,6 +24,7 @@
#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);
}
}
}

View file

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