Browse Source

Adding blur and sharpen

Former-commit-id: 3e988b0d327e4572f9ef3979cdfce5251ade3bd1
pull/17/head
James South 12 years ago
parent
commit
591d9b8a7c
  1. 28
      src/ImageProcessor/ImageFactory.cs
  2. 10
      src/ImageProcessor/ImageProcessor.csproj
  3. 499
      src/ImageProcessor/Imaging/Convolution.cs
  4. 13
      src/ImageProcessor/Imaging/Filters/ComicMatrixFilter.cs
  5. 179
      src/ImageProcessor/Imaging/GaussianLayer.cs
  6. 38
      src/ImageProcessor/Imaging/Pixel.cs
  7. 1
      src/ImageProcessor/Processors/Crop.cs
  8. 267
      src/ImageProcessor/Processors/GaussianBlur.cs
  9. 227
      src/ImageProcessor/Processors/GaussianSharpen.cs
  10. 1
      src/TestWebsites/NET45/Test_Website_NET45/Images/Thumbs.db.REMOVED.git-id
  11. BIN
      src/TestWebsites/NET45/Test_Website_NET45/Images/sample1.jpg
  12. BIN
      src/TestWebsites/NET45/Test_Website_NET45/Images/text.png
  13. 4
      src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml
  14. 14
      src/TestWebsites/NET45/Test_Website_NET45/config/imageprocessor/processing.config

28
src/ImageProcessor/ImageFactory.cs

@ -323,6 +323,34 @@ namespace ImageProcessor
return this;
}
/// <summary>
/// Applies a Gaussian blur to the current image.
/// </summary>
/// <param name="radius">
/// The radius by which to blur the images pixels.
/// Any integer between 0 and 100.
/// </param>
/// <returns>
/// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
/// </returns>
public ImageFactory Blur(int radius)
{
if (this.ShouldProcess)
{
// Sanitize the input.
if (radius > 100)
{
radius = 0;
}
GaussianBlur blur = new GaussianBlur { DynamicParameter = radius };
this.Image = blur.ProcessImage(this);
}
return this;
}
/// <summary>
/// Constrains the current image, resizing it to fit within the given dimensions whilst keeping its aspect ratio.
/// </summary>

10
src/ImageProcessor/ImageProcessor.csproj

@ -12,6 +12,8 @@
<AssemblyName>ImageProcessor</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -57,11 +59,15 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ExtBitmap.cs" />
<Compile Include="Helpers\Extensions\ImageExtensions.cs" />
<Compile Include="Helpers\Extensions\EnumExtensions.cs" />
<Compile Include="Helpers\Extensions\StringExtensions.cs" />
<Compile Include="ImageFactory.cs" />
<Compile Include="Imaging\AnchorPosition.cs" />
<Compile Include="Imaging\Convolution.cs" />
<Compile Include="Imaging\GaussianLayer.cs" />
<Compile Include="Imaging\Pixel.cs" />
<Compile Include="Imaging\ColorQuantizer.cs" />
<Compile Include="Imaging\ResizeLayer.cs" />
<Compile Include="Imaging\Filters\BlackWhiteMatrixFilter.cs" />
@ -83,8 +89,10 @@
<Compile Include="Imaging\RoundedCornerLayer.cs" />
<Compile Include="Imaging\TextLayer.cs" />
<Compile Include="Processors\Alpha.cs" />
<Compile Include="Processors\GaussianBlur.cs" />
<Compile Include="Processors\Brightness.cs" />
<Compile Include="Processors\Contrast.cs" />
<Compile Include="Processors\GaussianSharpen.cs" />
<Compile Include="Processors\RoundedCorners.cs" />
<Compile Include="Processors\Saturation.cs" />
<Compile Include="Processors\Flip.cs" />
@ -99,8 +107,8 @@
<Compile Include="Processors\Watermark.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

499
src/ImageProcessor/Imaging/Convolution.cs

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

13
src/ImageProcessor/Imaging/Filters/ComicMatrixFilter.cs

@ -1,9 +1,12 @@
// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ComicMatrixFilter.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// -----------------------------------------------------------------------
// <summary>
// Encapsulates methods with which to add a comic filter to an image.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Filters
{
@ -118,7 +121,7 @@ namespace ImageProcessor.Imaging.Filters
}
}
// Transfer the alpha channel from the mask to the hi sturation image.
// Transfer the alpha channel from the mask to the high saturation image.
TransferOneArgbChannelFromOneBitmapToAnother(patternBitmap, hisatchBitmap, ChannelArgb.Blue, ChannelArgb.Alpha);
// Overlay the image.

179
src/ImageProcessor/Imaging/GaussianLayer.cs

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

38
src/ImageProcessor/Imaging/Pixel.cs

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

1
src/ImageProcessor/Processors/Crop.cs

@ -145,6 +145,7 @@ namespace ImageProcessor.Processors
}
// Don't use an object initializer here.
// ReSharper disable once UseObjectOrCollectionInitializer
newImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppPArgb);
newImage.Tag = image.Tag;

267
src/ImageProcessor/Processors/GaussianBlur.cs

@ -1,32 +1,47 @@
// -----------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GaussianBlur.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// -----------------------------------------------------------------------
// <summary>
// Applies a Gaussian blur to the image.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Processors
{
#region Using
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text.RegularExpressions;
using ImageProcessor.Helpers.Extensions;
#endregion
using ImageProcessor.Imaging;
/// <summary>
/// Applies a Gaussian blur to the image.
/// Adapted from <see cref="http://code.google.com/p/imagelibrary/source/browse/trunk/Filters/GaussianBlurFilter.cs"/>
/// </summary>
public class GaussianBlur : IGraphicsProcessor
{
#region Fields
/// <summary>
/// The regular expression to search strings for.
/// </summary>
private static readonly Regex QueryRegex = new Regex(@"blur=(\d+(.\d+)?)", RegexOptions.Compiled);
private static readonly Regex QueryRegex = new Regex(@"blur=[^&]*", RegexOptions.Compiled);
/// <summary>
/// The blur regex.
/// </summary>
private static readonly Regex BlurRegex = new Regex(@"blur=\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>
@ -41,38 +56,24 @@ namespace ImageProcessor.Processors
}
/// <summary>
/// Gets or sets DynamicParameter.
/// Gets or sets the DynamicParameter.
/// </summary>
public dynamic DynamicParameter
{
get;
set;
}
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;
}
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;
}
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>
/// <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>
@ -91,7 +92,25 @@ namespace ImageProcessor.Processors
{
// Set the index on the first instance only.
this.SortOrder = match.Index;
this.DynamicParameter = match.Value.Split('=')[1];
// 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.ParseBlur(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;
@ -104,39 +123,33 @@ namespace ImageProcessor.Processors
/// <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>
/// <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.
/// The processed image from the current instance of the <see cref="T:ImageProcessor.ImageFactory" /> class.
/// </returns>
public Image ProcessImage(ImageFactory factory)
{
Bitmap newImage = null;
Image image = factory.Image;
Bitmap image = (Bitmap)factory.Image;
try
{
double radius = double.Parse(this.DynamicParameter);
byte[] sourceBytes = image.ToBytes(factory.ImageFormat);
byte[] destinationBytes = new byte[sourceBytes.Length];
this.ApplyGaussianBlur(image.Width, image.Height, radius, 1, ref sourceBytes, ref destinationBytes);
// Don't use an object initializer here.
newImage = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb);
newImage = (Bitmap)newImage.FromBytes(destinationBytes);
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.CreateGuassianBlurFilter(gaussianLayer.Size);
pixels = convolution.ProcessKernel(pixels, kernel);
newImage = convolution.PixelsToBitmap(pixels);
newImage.Tag = image.Tag;
image.Dispose();
image = newImage;
}
catch (Exception ex)
catch
{
var x = ex;
if (newImage != null)
{
newImage.Dispose();
@ -149,142 +162,66 @@ namespace ImageProcessor.Processors
#region Private
/// <summary>
/// The apply gaussian blur.
/// Returns the correct <see cref="T:System.Double"/> containing the sigma value
/// for the given string.
/// </summary>
/// <param name="width">
/// The width.
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <param name="height">
/// The height.
/// </param>
/// <param name="radius">
/// The radius.
/// </param>
/// <param name="sourceBytes">
/// The source bytes array.
/// </param>
/// <param name="destinationBytes">
/// The destination byte array.
/// </param>
private void ApplyGaussianBlur(int width, int height, double radius, double amount, ref byte[] src, ref byte[] dst)
/// <returns>
/// The correct <see cref="T:System.Double"/> for the given string.
/// </returns>
private double ParseSigma(string input)
{
int shift, dest, source;
int blurDiam = (int)Math.Pow(radius, 2);
int gaussWidth = (blurDiam * 2) + 1;
double[] kernel = CreateKernel(gaussWidth, blurDiam);
// Calculate the sum of the Gaussian kernel
double gaussSum = 0;
for (int n = 0; n < gaussWidth; n++)
foreach (Match match in SigmaRegex.Matches(input))
{
gaussSum += kernel[n];
// split on text-
return Convert.ToDouble(match.Value.Split('-')[1]);
}
// Scale the Gaussian kernel
for (int n = 0; n < gaussWidth; n++)
{
kernel[n] = kernel[n] / gaussSum;
}
//premul = kernel[k] / gaussSum;
// Create an X & Y pass buffer
byte[] gaussPassX = new byte[src.Length];
return 1.4d;
}
// Do Horizontal Pass
for (int y = 0; y < height; y++)
/// <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))
{
for (int x = 0; x < width; x++)
{
dest = y * width + x;
// Iterate through kernel
for (int k = 0; k < gaussWidth; k++)
{
// Get pixel-shift (pixel dist between dest and source)
shift = k - blurDiam;
// Basic edge clamp
source = dest + shift;
if (x + shift <= 0 || x + shift >= width)
{
source = dest;
}
// Combine source and destination pixels with Gaussian Weight
gaussPassX[(dest << 2) + 2] = (byte)(gaussPassX[(dest << 2) + 2] + (src[(source << 2) + 2]) * kernel[k]);
gaussPassX[(dest << 2) + 1] = (byte)(gaussPassX[(dest << 2) + 1] + (src[(source << 2) + 1]) * kernel[k]);
gaussPassX[(dest << 2)] = (byte)(gaussPassX[(dest << 2)] + (src[(source << 2)]) * kernel[k]);
}
}
// split on text-
return Convert.ToInt32(match.Value.Split('-')[1]);
}
// Do Vertical Pass
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
dest = y * width + x;
// Iterate through kernel
for (int k = 0; k < gaussWidth; k++)
{
// Get pixel-shift (pixel dist between dest and source)
shift = k - blurDiam;
// Basic edge clamp
source = dest + (shift * width);
if (y + shift <= 0 || y + shift >= height)
{
source = dest;
}
// Combine source and destination pixels with Gaussian Weight
dst[(dest << 2) + 2] = (byte)(dst[(dest << 2) + 2] + (gaussPassX[(source << 2) + 2]) * kernel[k]);
dst[(dest << 2) + 1] = (byte)(dst[(dest << 2) + 1] + (gaussPassX[(source << 2) + 1]) * kernel[k]);
dst[(dest << 2)] = (byte)(dst[(dest << 2)] + (gaussPassX[(source << 2)]) * kernel[k]);
}
}
}
return 0;
}
/// <summary>
/// Creates the Gaussian kernel.
/// Returns the correct <see cref="T:System.Int32"/> containing the blur value
/// for the given string.
/// </summary>
/// <param name="gaussianWidth">
/// The gaussian width.
/// </param>
/// <param name="blurDiameter">
/// The blur diameter.
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The <see cref="double"/>.
/// The correct <see cref="T:System.Int32"/> for the given string.
/// </returns>
private double[] CreateKernel(int gaussianWidth, int blurDiam)
private int ParseBlur(string input)
{
double[] kernel = new double[gaussianWidth];
// Set the maximum value of the Gaussian curve
const double sd = 255;
// Set the width of the Gaussian curve
double range = gaussianWidth;
// Set the average value of the Gaussian curve
double mean = (range / sd);
// Set first half of Gaussian curve in kernel
for (int pos = 0, len = blurDiam + 1; pos < len; pos++)
foreach (Match match in BlurRegex.Matches(input))
{
// Distribute Gaussian curve across kernel[array]
kernel[gaussianWidth - 1 - pos] = kernel[pos] = Math.Sqrt(Math.Sin((((pos + 1) * (Math.PI / 2)) - mean) / range)) * sd;
// split on text-
return Convert.ToInt32(match.Value.Split('=')[1]);
}
return kernel;
return 0;
}
#endregion
}

227
src/ImageProcessor/Processors/GaussianSharpen.cs

@ -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
src/TestWebsites/NET45/Test_Website_NET45/Images/Thumbs.db.REMOVED.git-id

@ -1 +0,0 @@
90ba9cd46c99300489b004f2b4df360b84e4444d

BIN
src/TestWebsites/NET45/Test_Website_NET45/Images/sample1.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/TestWebsites/NET45/Test_Website_NET45/Images/text.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

4
src/TestWebsites/NET45/Test_Website_NET45/Views/Home/Index.cshtml

@ -8,6 +8,8 @@
<div class="col-s-6">
<h2>Resized</h2>
<img src="/images/Penguins.jpg?width=300" />
<img src="/images/Penguins.jpg?width=300&blur=7" />
<img src="/images/Penguins.jpg?width=300&sharpen=7" />
<h3>Foreign language test.</h3>
<img src="/images/udendørs.jpg?width=300" />
<img src="/images/udendørs.jpg?preset=demo&filter=comic" />
@ -51,7 +53,7 @@
</div>
</div>
</section>
<section>
<section>
<div class="row">
<h2>Resize Stretch</h2>
<div class="col-s-4">

14
src/TestWebsites/NET45/Test_Website_NET45/config/imageprocessor/processing.config

@ -7,6 +7,20 @@
<setting key="MaxHeight" value="3000"/>
</settings>
</plugin>
<plugin name="GaussianBlur">
<settings>
<setting key="MaxSize" value="22"/>
<setting key="MaxSigma" value="5.1"/>
<setting key="MaxThreshold" value="100"/>
</settings>
</plugin>
<plugin name="GaussianSharpen">
<settings>
<setting key="MaxSize" value="22"/>
<setting key="MaxSigma" value="5.1"/>
<setting key="MaxThreshold" value="100"/>
</settings>
</plugin>
<plugin name="Preset">
<settings>
<setting key="demo" value="width=300&#038;height=150"/>

Loading…
Cancel
Save