// -------------------------------------------------------------------------------------------------------------------- // // 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 } }