mirror of https://github.com/SixLabors/ImageSharp
14 changed files with 1108 additions and 173 deletions
@ -0,0 +1,499 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="Convolution.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Provides methods for applying blurring and sharpening effects to an image..
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Imaging |
|||
{ |
|||
using System; |
|||
using System.Drawing; |
|||
using System.Drawing.Imaging; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods for applying blurring and sharpening effects to an image..
|
|||
/// </summary>
|
|||
public class Convolution |
|||
{ |
|||
/// <summary>
|
|||
/// The standard deviation 'sigma' value for calculating Gaussian curves.
|
|||
/// </summary>
|
|||
private readonly double standardDeviation = 1.4; |
|||
|
|||
/// <summary>
|
|||
/// Whether to use use dynamic divider for edges.
|
|||
/// </summary>
|
|||
private bool useDynamicDividerForEdges = true; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Convolution"/> class.
|
|||
/// </summary>
|
|||
public Convolution() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Convolution"/> class.
|
|||
/// </summary>
|
|||
/// <param name="standardDeviation">
|
|||
/// The standard deviation.
|
|||
/// </param>
|
|||
public Convolution(double standardDeviation) |
|||
{ |
|||
this.standardDeviation = standardDeviation; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the threshold to add to the weighted sum.
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// Specifies the threshold value, which is added to each weighted
|
|||
/// sum of pixels.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public int Threshold { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the value used to divide convolution; the weighted sum
|
|||
/// of pixels is divided by this value.
|
|||
/// <remarks>
|
|||
/// <para>If not set this value will be automatically calculated.</para>
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public double Divider { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to use the dynamic divider for edges.
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// If it is set to <see langword="false" />, then the same divider, specified by <see cref="Divider" />
|
|||
/// property or calculated automatically, will be applied both for non-edge regions
|
|||
/// and for edge regions. If the value is set to <see langword="true" />, then the dynamically
|
|||
/// calculated divider will be used for edge regions. This is calculated from the sum of the kernel
|
|||
/// elements used at that region, which are taken into account for particular processed pixel
|
|||
/// (elements, which are not outside image).
|
|||
/// </para>
|
|||
/// <para>Default value is set to <see langword="true" />.</para>
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public bool UseDynamicDividerForEdges |
|||
{ |
|||
get |
|||
{ |
|||
return this.useDynamicDividerForEdges; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
this.useDynamicDividerForEdges = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
|
|||
/// </summary>
|
|||
/// <param name="kernelSize">Kernel Size</param>
|
|||
/// <returns>A Gaussian Kernel with the given size and deviation.</returns>
|
|||
public double[,] CreateGaussianKernel(int kernelSize) |
|||
{ |
|||
double[,] kernel = new double[kernelSize, 1]; |
|||
double sum = 0.0d; |
|||
|
|||
int midpoint = kernelSize / 2; |
|||
for (int i = 0; i < kernelSize; i++) |
|||
{ |
|||
int x = i - midpoint; |
|||
double gx = this.Gaussian(x); |
|||
sum += gx; |
|||
kernel[i, 0] = gx; |
|||
} |
|||
|
|||
// Normalise kernel so that the sum of all weights equals 1
|
|||
for (int i = 0; i < kernelSize; i++) |
|||
{ |
|||
kernel[i, 0] = kernel[i, 0] / sum; |
|||
} |
|||
|
|||
return kernel; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function
|
|||
/// </summary>
|
|||
/// <param name="kernelSize">Kernel Size</param>
|
|||
/// <returns>A Gaussian Kernel with the given size and deviation.</returns>
|
|||
public double[,] CreateGaussianKernel2D(int kernelSize) |
|||
{ |
|||
double[,] kernel = new double[kernelSize, kernelSize]; |
|||
|
|||
int midpoint = kernelSize / 2; |
|||
|
|||
for (int i = 0; i < kernelSize; i++) |
|||
{ |
|||
int x = i - midpoint; |
|||
|
|||
for (int j = 0; j < kernelSize; j++) |
|||
{ |
|||
int y = j - midpoint; |
|||
double gxy = this.Gaussian2D(x, y); |
|||
kernel[i, j] = gxy; |
|||
} |
|||
} |
|||
|
|||
return kernel; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function for
|
|||
/// blurring images.
|
|||
/// </summary>
|
|||
/// <param name="kernelSize">Kernel Size</param>
|
|||
/// <returns>A Gaussian Kernel with the given size.</returns>
|
|||
public double[,] CreateGuassianBlurFilter(int kernelSize) |
|||
{ |
|||
// Create kernel
|
|||
double[,] kernel = this.CreateGaussianKernel2D(kernelSize); |
|||
double min = kernel[0, 0]; |
|||
|
|||
// Convert to integer blurring kernel. First of all the integer kernel is calculated from Kernel2D
|
|||
// by dividing all elements by the element with the smallest value.
|
|||
double[,] intKernel = new double[kernelSize, kernelSize]; |
|||
int divider = 0; |
|||
|
|||
for (int i = 0; i < kernelSize; i++) |
|||
{ |
|||
for (int j = 0; j < kernelSize; j++) |
|||
{ |
|||
double v = kernel[i, j] / min; |
|||
|
|||
if (v > ushort.MaxValue) |
|||
{ |
|||
v = ushort.MaxValue; |
|||
} |
|||
|
|||
intKernel[i, j] = (int)v; |
|||
|
|||
// Collect the divider
|
|||
divider += (int)intKernel[i, j]; |
|||
} |
|||
} |
|||
|
|||
// Update filter
|
|||
this.Divider = divider; |
|||
return intKernel; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function for
|
|||
/// sharpening images.
|
|||
/// </summary>
|
|||
/// <param name="kernelSize">Kernel Size</param>
|
|||
/// <returns>A Gaussian Kernel with the given size.</returns>
|
|||
public double[,] CreateGuassianSharpenFilter(int kernelSize) |
|||
{ |
|||
// Create kernel
|
|||
double[,] kernel = this.CreateGaussianKernel2D(kernelSize); |
|||
double min = kernel[0, 0]; |
|||
|
|||
// integer kernel
|
|||
double[,] intKernel = new double[kernelSize, kernelSize]; |
|||
int sum = 0; |
|||
int divider = 0; |
|||
|
|||
for (int i = 0; i < kernelSize; i++) |
|||
{ |
|||
for (int j = 0; j < kernelSize; j++) |
|||
{ |
|||
double v = kernel[i, j] / min; |
|||
|
|||
if (v > ushort.MaxValue) |
|||
{ |
|||
v = ushort.MaxValue; |
|||
} |
|||
|
|||
intKernel[i, j] = (int)v; |
|||
|
|||
// Collect the sum.
|
|||
sum += (int)intKernel[i, j]; |
|||
} |
|||
} |
|||
|
|||
// Recalculate the kernel.
|
|||
int center = kernelSize >> 1; |
|||
|
|||
for (int i = 0; i < kernelSize; i++) |
|||
{ |
|||
for (int j = 0; j < kernelSize; j++) |
|||
{ |
|||
if ((i == center) && (j == center)) |
|||
{ |
|||
// Calculate central value
|
|||
intKernel[i, j] = (2 * sum) - intKernel[i, j]; |
|||
} |
|||
else |
|||
{ |
|||
// invert value
|
|||
intKernel[i, j] = -intKernel[i, j]; |
|||
} |
|||
|
|||
// Collect the divider
|
|||
divider += (int)intKernel[i, j]; |
|||
} |
|||
} |
|||
|
|||
// Update filter
|
|||
this.Divider = divider; |
|||
return intKernel; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert a Bitmap to an a multidimensional array of raw pixel values
|
|||
/// </summary>
|
|||
/// <param name="image">The image to convert</param>
|
|||
/// <returns>a multidimensional array of raw pixel values</returns>
|
|||
public Pixel[,] BitmapToPixels(Bitmap image) |
|||
{ |
|||
Pixel[,] pixels = new Pixel[image.Width, image.Height]; |
|||
|
|||
BitmapData sourceData = image.LockBits( |
|||
new Rectangle(0, 0, image.Width, image.Height), |
|||
ImageLockMode.ReadOnly, |
|||
image.PixelFormat); |
|||
|
|||
byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height]; |
|||
|
|||
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length); |
|||
image.UnlockBits(sourceData); |
|||
|
|||
int pixelSize = Image.GetPixelFormatSize(image.PixelFormat) / 8; |
|||
int stride = sourceData.Stride; |
|||
|
|||
for (int x = 0; x < image.Width; x++) |
|||
{ |
|||
for (int y = 0; y < image.Height; y++) |
|||
{ |
|||
int byteOffset = (y * stride) + (x * pixelSize); |
|||
|
|||
pixels[x, y] = new Pixel |
|||
{ |
|||
A = pixelBuffer[byteOffset + 3], |
|||
R = pixelBuffer[byteOffset + 2], |
|||
G = pixelBuffer[byteOffset + 1], |
|||
B = pixelBuffer[byteOffset] |
|||
}; |
|||
} |
|||
} |
|||
|
|||
return pixels; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert a multidimensional array of raw pixel values to a bitmap
|
|||
/// </summary>
|
|||
/// <param name="pixels">The pixels to convert</param>
|
|||
/// <returns>a bitmap</returns>
|
|||
public Bitmap PixelsToBitmap(Pixel[,] pixels) |
|||
{ |
|||
int width = pixels.GetLength(0); |
|||
int height = pixels.GetLength(1); |
|||
Bitmap resultBitmap = new Bitmap(width, height); |
|||
BitmapData resultData = resultBitmap.LockBits( |
|||
new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), |
|||
ImageLockMode.WriteOnly, |
|||
PixelFormat.Format32bppArgb); |
|||
|
|||
byte[] pixelBuffer = new byte[resultData.Stride * resultData.Height]; |
|||
int stride = resultData.Stride; |
|||
int pixelSize = Image.GetPixelFormatSize(resultBitmap.PixelFormat) / 8; |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
int byteOffset = (y * stride) + (x * pixelSize); |
|||
|
|||
double pixelRed = pixels[x, y].R; |
|||
double pixelGreen = pixels[x, y].G; |
|||
double pixelBlue = pixels[x, y].B; |
|||
double pixelAlpha = pixels[x, y].A; |
|||
|
|||
pixelBuffer[byteOffset] = (byte)pixelBlue; |
|||
pixelBuffer[byteOffset + 1] = (byte)pixelGreen; |
|||
pixelBuffer[byteOffset + 2] = (byte)pixelRed; |
|||
pixelBuffer[byteOffset + 3] = (byte)pixelAlpha; |
|||
} |
|||
} |
|||
|
|||
Marshal.Copy(pixelBuffer, 0, resultData.Scan0, pixelBuffer.Length); |
|||
resultBitmap.UnlockBits(resultData); |
|||
|
|||
return resultBitmap; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the given kernel to produce an array of pixels representing a bitmap.
|
|||
/// </summary>
|
|||
/// <param name="pixels">The raw pixels of the image to blur</param>
|
|||
/// <param name="kernel">The Gaussian kernel to use when performing the method</param>
|
|||
/// <returns>An array of pixels representing the bitmap.</returns>
|
|||
public Pixel[,] ProcessKernel(Pixel[,] pixels, double[,] kernel) |
|||
{ |
|||
int width = pixels.GetLength(0); |
|||
int height = pixels.GetLength(1); |
|||
int kernelLength = kernel.GetLength(0); |
|||
int radius = kernelLength >> 1; |
|||
int kernelSize = kernelLength * kernelLength; |
|||
Pixel[,] result = new Pixel[width, height]; |
|||
|
|||
// For each line
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
// For each pixel
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
// The number of kernel elements taken into account
|
|||
int processedKernelSize; |
|||
|
|||
// Colour sums
|
|||
double blue; |
|||
double alpha; |
|||
double divider; |
|||
double green; |
|||
double red = green = blue = alpha = divider = processedKernelSize = 0; |
|||
|
|||
// For each kernel row
|
|||
for (int i = 0; i < kernelLength; i++) |
|||
{ |
|||
int ir = i - radius; |
|||
int offsetY = y + ir; |
|||
|
|||
// Skip the current row
|
|||
if (offsetY < 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// Outwith the current bounds so break.
|
|||
if (offsetY >= height) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
// For each kernel column
|
|||
for (int j = 0; j < kernelLength; j++) |
|||
{ |
|||
int jr = j - radius; |
|||
int offsetX = x + jr; |
|||
|
|||
// Skip the column
|
|||
if (offsetX < 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (offsetX < width) |
|||
{ |
|||
double k = kernel[i, j]; |
|||
Pixel pixel = pixels[offsetX, offsetY]; |
|||
|
|||
divider += k; |
|||
|
|||
red += k * pixel.R; |
|||
green += k * pixel.G; |
|||
blue += k * pixel.B; |
|||
alpha += k * pixel.A; |
|||
|
|||
processedKernelSize++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Check to see if all kernel elements were processed
|
|||
if (processedKernelSize == kernelSize) |
|||
{ |
|||
// All kernel elements are processed; we are not on the edge.
|
|||
divider = this.Divider; |
|||
} |
|||
else |
|||
{ |
|||
// We are on an edge; do we need to use dynamic divider or not?
|
|||
if (!this.UseDynamicDividerForEdges) |
|||
{ |
|||
// Apply the set divider.
|
|||
divider = this.Divider; |
|||
} |
|||
} |
|||
|
|||
// Check and apply the divider
|
|||
if ((long)divider != 0) |
|||
{ |
|||
red /= divider; |
|||
green /= divider; |
|||
blue /= divider; |
|||
alpha /= divider; |
|||
} |
|||
|
|||
// Add any applicable threshold.
|
|||
red += this.Threshold; |
|||
green += this.Threshold; |
|||
blue += this.Threshold; |
|||
alpha += this.Threshold; |
|||
|
|||
result[x, y].R = (byte)((red > 255) ? 255 : ((red < 0) ? 0 : red)); |
|||
result[x, y].G = (byte)((green > 255) ? 255 : ((green < 0) ? 0 : green)); |
|||
result[x, y].B = (byte)((blue > 255) ? 255 : ((blue < 0) ? 0 : blue)); |
|||
result[x, y].A = (byte)((alpha > 255) ? 255 : ((alpha < 0) ? 0 : alpha)); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
#region Private
|
|||
/// <summary>
|
|||
/// Implementation of 1D Gaussian G(x) function
|
|||
/// </summary>
|
|||
/// <param name="x">The x provided to G(x)</param>
|
|||
/// <returns>The Gaussian G(x)</returns>
|
|||
private double Gaussian(double x) |
|||
{ |
|||
const double Numerator = 1.0; |
|||
double denominator = Math.Sqrt(2 * Math.PI) * this.standardDeviation; |
|||
|
|||
double exponentNumerator = -x * x; |
|||
double exponentDenominator = 2 * Math.Pow(this.standardDeviation, 2); |
|||
|
|||
double left = Numerator / denominator; |
|||
double right = Math.Exp(exponentNumerator / exponentDenominator); |
|||
|
|||
return left * right; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implementation of 2D Gaussian G(x) function
|
|||
/// </summary>
|
|||
/// <param name="x">The x provided to G(x y)</param>
|
|||
/// <param name="y">The y provided to G(x y)</param>
|
|||
/// <returns>The Gaussian G(x y)</returns>
|
|||
private double Gaussian2D(double x, double y) |
|||
{ |
|||
const double Numerator = 1.0; |
|||
double denominator = (2 * Math.PI) * Math.Pow(this.standardDeviation, 2); |
|||
|
|||
double exponentNumerator = (-x * x) + (-y * y); |
|||
double exponentDenominator = 2 * Math.Pow(this.standardDeviation, 2); |
|||
|
|||
double left = Numerator / denominator; |
|||
double right = Math.Exp(exponentNumerator / exponentDenominator); |
|||
|
|||
return left * right; |
|||
} |
|||
#endregion
|
|||
} |
|||
} |
|||
@ -0,0 +1,179 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="GaussianLayer.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// A Gaussian layer for applying sharpening and blurring methods to an image.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Imaging |
|||
{ |
|||
using System; |
|||
|
|||
/// <summary>
|
|||
/// A Gaussian layer for applying sharpening and blurring methods to an image.
|
|||
/// </summary>
|
|||
public class GaussianLayer |
|||
{ |
|||
/// <summary>
|
|||
/// The size.
|
|||
/// </summary>
|
|||
private int size; |
|||
|
|||
/// <summary>
|
|||
/// The sigma.
|
|||
/// </summary>
|
|||
private double sigma; |
|||
|
|||
/// <summary>
|
|||
/// The threshold.
|
|||
/// </summary>
|
|||
private int threshold; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="GaussianLayer"/> class.
|
|||
/// </summary>
|
|||
public GaussianLayer() |
|||
{ |
|||
this.Size = 3; |
|||
this.Sigma = 1.4; |
|||
this.Threshold = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="GaussianLayer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="size">
|
|||
/// The size to set the Gaussian kernel to.
|
|||
/// </param>
|
|||
/// <param name="sigma">
|
|||
/// The Sigma value (standard deviation) for Gaussian function used to calculate the kernel.
|
|||
/// </param>
|
|||
/// <param name="threshold">
|
|||
/// The threshold value, which is added to each weighted sum of pixels.
|
|||
/// </param>
|
|||
public GaussianLayer(int size, double sigma = 1.4, int threshold = 0) |
|||
{ |
|||
this.Size = size; |
|||
this.Sigma = sigma; |
|||
this.Threshold = threshold; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the size of the Gaussian kernel.
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// If set to a value below 0, the property will be set to 0.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public int Size |
|||
{ |
|||
get |
|||
{ |
|||
return this.size; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (value < 0) |
|||
{ |
|||
value = 0; |
|||
} |
|||
|
|||
this.size = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the sigma value (standard deviation) for Gaussian function used to calculate the kernel.
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// If set to a value below 0, the property will be set to 0.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public double Sigma |
|||
{ |
|||
get |
|||
{ |
|||
return this.sigma; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (value < 0) |
|||
{ |
|||
value = 0; |
|||
} |
|||
|
|||
this.sigma = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the threshold value, which is added to each weighted sum of pixels.
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// If set to a value below 0, the property will be set to 0.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public int Threshold |
|||
{ |
|||
get |
|||
{ |
|||
return this.threshold; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (value < 0) |
|||
{ |
|||
value = 0; |
|||
} |
|||
|
|||
this.threshold = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value that indicates whether the specified object is an
|
|||
/// <see cref="GaussianLayer"/> object that is equivalent to
|
|||
/// this <see cref="GaussianLayer"/> object.
|
|||
/// </summary>
|
|||
/// <param name="obj">
|
|||
/// The object to test.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the given object is an <see cref="GaussianLayer"/> object that is equivalent to
|
|||
/// this <see cref="GaussianLayer"/> object; otherwise, false.
|
|||
/// </returns>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
GaussianLayer gaussianLayer = obj as GaussianLayer; |
|||
|
|||
if (gaussianLayer == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return this.Size == gaussianLayer.Size |
|||
&& Math.Abs(this.Sigma - gaussianLayer.Sigma) < 0.0001 |
|||
&& this.Threshold == gaussianLayer.Threshold; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a hash code value that represents this object.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// A hash code that represents this object.
|
|||
/// </returns>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return this.Size.GetHashCode() + this.Sigma.GetHashCode() + this.Threshold.GetHashCode(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="Pixel.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Represents a single pixel.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Imaging |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a single pixel.
|
|||
/// </summary>
|
|||
public struct Pixel |
|||
{ |
|||
/// <summary>
|
|||
/// The red component of the pixel.
|
|||
/// </summary>
|
|||
public double R; |
|||
|
|||
/// <summary>
|
|||
/// The green component of the pixel.
|
|||
/// </summary>
|
|||
public double G; |
|||
|
|||
/// <summary>
|
|||
/// The blue component of the pixel.
|
|||
/// </summary>
|
|||
public double B; |
|||
|
|||
/// <summary>
|
|||
/// The alpha component of the pixel.
|
|||
/// </summary>
|
|||
public double A; |
|||
} |
|||
} |
|||
@ -0,0 +1,227 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="GaussianSharpen.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// Applies a Gaussian sharpen to the image.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Processors |
|||
{ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Drawing; |
|||
using System.Text.RegularExpressions; |
|||
using ImageProcessor.Imaging; |
|||
|
|||
/// <summary>
|
|||
/// Applies a Gaussian sharpen to the image.
|
|||
/// </summary>
|
|||
public class GaussianSharpen : IGraphicsProcessor |
|||
{ |
|||
#region Fields
|
|||
/// <summary>
|
|||
/// The regular expression to search strings for.
|
|||
/// </summary>
|
|||
private static readonly Regex QueryRegex = new Regex(@"sharpen=[^&]*", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// The sharpen regex.
|
|||
/// </summary>
|
|||
private static readonly Regex SharpenRegex = new Regex(@"sharpen=\d+", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// The sigma regex.
|
|||
/// </summary>
|
|||
private static readonly Regex SigmaRegex = new Regex(@"sigma-\d+(.+\d+)?", RegexOptions.Compiled); |
|||
|
|||
/// <summary>
|
|||
/// The threshold regex.
|
|||
/// </summary>
|
|||
private static readonly Regex ThresholdRegex = new Regex(@"threshold-\d+", RegexOptions.Compiled); |
|||
#endregion
|
|||
#region IGraphicsProcessor Members
|
|||
/// <summary>
|
|||
/// Gets the regular expression to search strings for.
|
|||
/// </summary>
|
|||
public Regex RegexPattern |
|||
{ |
|||
get |
|||
{ |
|||
return QueryRegex; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the DynamicParameter.
|
|||
/// </summary>
|
|||
public dynamic DynamicParameter { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the order in which this processor is to be used in a chain.
|
|||
/// </summary>
|
|||
public int SortOrder { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets any additional settings required by the processor.
|
|||
/// </summary>
|
|||
public Dictionary<string, string> Settings { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The position in the original string where the first character of the captured substring was found.
|
|||
/// </summary>
|
|||
/// <param name="queryString">The query string to search.</param>
|
|||
/// <returns>
|
|||
/// The zero-based starting position in the original string where the captured substring was found.
|
|||
/// </returns>
|
|||
public int MatchRegexIndex(string queryString) |
|||
{ |
|||
int index = 0; |
|||
|
|||
// Set the sort order to max to allow filtering.
|
|||
this.SortOrder = int.MaxValue; |
|||
|
|||
foreach (Match match in this.RegexPattern.Matches(queryString)) |
|||
{ |
|||
if (match.Success) |
|||
{ |
|||
if (index == 0) |
|||
{ |
|||
// Set the index on the first instance only.
|
|||
this.SortOrder = match.Index; |
|||
|
|||
// Normalise and set the variables.
|
|||
int maxSize; |
|||
double maxSigma; |
|||
int maxThreshold; |
|||
|
|||
int.TryParse(this.Settings["MaxSize"], out maxSize); |
|||
double.TryParse(this.Settings["MaxSigma"], out maxSigma); |
|||
int.TryParse(this.Settings["MaxThreshold"], out maxThreshold); |
|||
|
|||
int size = this.ParseSharpen(match.Value); |
|||
double sigma = this.ParseSigma(match.Value); |
|||
int threshold = this.ParseThreshold(match.Value); |
|||
|
|||
size = maxSize < size ? maxSize : size; |
|||
sigma = maxSigma < sigma ? maxSigma : sigma; |
|||
threshold = maxThreshold < threshold ? maxThreshold : threshold; |
|||
|
|||
this.DynamicParameter = new GaussianLayer(size, sigma, threshold); |
|||
} |
|||
|
|||
index += 1; |
|||
} |
|||
} |
|||
|
|||
return this.SortOrder; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Processes the image.
|
|||
/// </summary>
|
|||
/// <param name="factory">The the current instance of the <see cref="T:ImageProcessor.ImageFactory" /> class containing
|
|||
/// the image to process.</param>
|
|||
/// <returns>
|
|||
/// The processed image from the current instance of the <see cref="T:ImageProcessor.ImageFactory" /> class.
|
|||
/// </returns>
|
|||
public Image ProcessImage(ImageFactory factory) |
|||
{ |
|||
Bitmap newImage = null; |
|||
Bitmap image = (Bitmap)factory.Image; |
|||
|
|||
try |
|||
{ |
|||
newImage = new Bitmap(image); |
|||
GaussianLayer gaussianLayer = (GaussianLayer)this.DynamicParameter; |
|||
|
|||
Convolution convolution = new Convolution(gaussianLayer.Sigma) { Threshold = gaussianLayer.Threshold }; |
|||
Pixel[,] pixels = convolution.BitmapToPixels(newImage); |
|||
double[,] kernel = convolution.CreateGuassianSharpenFilter(gaussianLayer.Size); |
|||
pixels = convolution.ProcessKernel(pixels, kernel); |
|||
newImage = convolution.PixelsToBitmap(pixels); |
|||
newImage.Tag = image.Tag; |
|||
|
|||
image.Dispose(); |
|||
image = newImage; |
|||
} |
|||
catch |
|||
{ |
|||
if (newImage != null) |
|||
{ |
|||
newImage.Dispose(); |
|||
} |
|||
} |
|||
|
|||
return image; |
|||
} |
|||
#endregion
|
|||
|
|||
#region Private
|
|||
/// <summary>
|
|||
/// Returns the correct <see cref="T:System.Double"/> containing the sigma value
|
|||
/// for the given string.
|
|||
/// </summary>
|
|||
/// <param name="input">
|
|||
/// The input string containing the value to parse.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The correct <see cref="T:System.Double"/> for the given string.
|
|||
/// </returns>
|
|||
private double ParseSigma(string input) |
|||
{ |
|||
foreach (Match match in SigmaRegex.Matches(input)) |
|||
{ |
|||
// split on text-
|
|||
return Convert.ToDouble(match.Value.Split('-')[1]); |
|||
} |
|||
|
|||
return 1.4d; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the correct <see cref="T:System.Int32"/> containing the threshold value
|
|||
/// for the given string.
|
|||
/// </summary>
|
|||
/// <param name="input">
|
|||
/// The input string containing the value to parse.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The correct <see cref="T:System.Int32"/> for the given string.
|
|||
/// </returns>
|
|||
private int ParseThreshold(string input) |
|||
{ |
|||
foreach (Match match in ThresholdRegex.Matches(input)) |
|||
{ |
|||
// split on text-
|
|||
return Convert.ToInt32(match.Value.Split('-')[1]); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the correct <see cref="T:System.Int32"/> containing the sharpen value
|
|||
/// for the given string.
|
|||
/// </summary>
|
|||
/// <param name="input">
|
|||
/// The input string containing the value to parse.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The correct <see cref="T:System.Int32"/> for the given string.
|
|||
/// </returns>
|
|||
private int ParseSharpen(string input) |
|||
{ |
|||
foreach (Match match in SharpenRegex.Matches(input)) |
|||
{ |
|||
// split on text-
|
|||
return Convert.ToInt32(match.Value.Split('=')[1]); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
#endregion
|
|||
} |
|||
} |
|||
@ -1 +0,0 @@ |
|||
90ba9cd46c99300489b004f2b4df360b84e4444d |
|||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
Loading…
Reference in new issue