// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
//
//
// Provides methods for applying blurring and sharpening effects to an image..
//
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
{
using System;
using System.Drawing;
using System.Threading.Tasks;
using ImageProcessor.Common.Extensions;
///
/// Provides methods for applying blurring and sharpening effects to an image..
///
public class Convolution
{
///
/// The standard deviation 'sigma' value for calculating Gaussian curves.
///
private readonly double standardDeviation = 1.4;
///
/// Whether to use dynamic divider for edges.
///
private bool useDynamicDividerForEdges = true;
///
/// Initializes a new instance of the class.
///
public Convolution()
{
}
///
/// Initializes a new instance of the class.
///
///
/// The standard deviation.
///
public Convolution(double standardDeviation)
{
this.standardDeviation = standardDeviation;
}
///
/// Gets or sets the threshold to add to the weighted sum.
///
///
/// Specifies the threshold value, which is added to each weighted
/// sum of pixels.
///
///
///
public int Threshold { get; set; }
///
/// Gets or sets the value used to divide convolution; the weighted sum
/// of pixels is divided by this value.
///
/// If not set this value will be automatically calculated.
///
///
public double Divider { get; set; }
///
/// Gets or sets a value indicating whether to use the dynamic divider for edges.
///
///
/// If it is set to , then the same divider, specified by
/// property or calculated automatically, will be applied both for non-edge regions
/// and for edge regions. If the value is set to , 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).
///
/// Default value is set to .
///
///
public bool UseDynamicDividerForEdges
{
get
{
return this.useDynamicDividerForEdges;
}
set
{
this.useDynamicDividerForEdges = value;
}
}
///
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
///
/// Kernel Size
/// A Gaussian Kernel with the given size and deviation.
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;
}
///
/// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function
///
/// Kernel Size
/// A Gaussian Kernel with the given size and deviation.
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;
}
///
/// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function for
/// blurring images.
///
/// Kernel Size
/// A Gaussian Kernel with the given size.
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;
}
///
/// Create a 2 dimensional Gaussian kernel using the Gaussian G(x y) function for
/// sharpening images.
///
/// Kernel Size
/// A Gaussian Kernel with the given size.
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;
}
///
/// Processes the given kernel to produce an array of pixels representing a bitmap.
///
/// The image to process.
/// The Gaussian kernel to use when performing the method
/// A processed bitmap.
public Bitmap ProcessKernel(Bitmap source, double[,] kernel)
{
int width = source.Width;
int height = source.Height;
Bitmap destination = new Bitmap(width, height);
destination.SetResolution(source.HorizontalResolution, source.VerticalResolution);
using (FastBitmap sourceBitmap = new FastBitmap(source))
{
using (FastBitmap destinationBitmap = new FastBitmap(destination))
{
int kernelLength = kernel.GetLength(0);
int radius = kernelLength >> 1;
int kernelSize = kernelLength * kernelLength;
int threshold = this.Threshold;
// For each line
Parallel.For(
0,
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)
{
// ReSharper disable once AccessToDisposedClosure
Color color = sourceBitmap.GetPixel(offsetX, offsetY);
double k = kernel[i, j];
divider += k;
red += k * color.R;
green += k * color.G;
blue += k * color.B;
alpha += k * color.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 += threshold;
green += threshold;
blue += threshold;
alpha += threshold;
// ReSharper disable once AccessToDisposedClosure
destinationBitmap.SetPixel(x, y, Color.FromArgb(alpha.ToByte(), red.ToByte(), green.ToByte(), blue.ToByte()));
}
});
}
}
return destination;
}
#region Private
///
/// Implementation of 1D Gaussian G(x) function
///
/// The x provided to G(x)
/// The Gaussian G(x)
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;
}
///
/// Implementation of 2D Gaussian G(x) function
///
/// The x provided to G(x y)
/// The y provided to G(x y)
/// The Gaussian G(x y)
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
}
}