Browse Source

Further refactor on Gaussian stuff

pull/904/head
Anton Firszov 7 years ago
parent
commit
a117dbb873
  1. 26
      src/ImageSharp/Processing/GaussianSharpenExtensions.cs
  2. 93
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs
  3. 16
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
  4. 37
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
  5. 101
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
  6. 42
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
  7. 6
      tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs

26
src/ImageSharp/Processing/GaussianSharpenExtensions.cs

@ -1,50 +1,46 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Processing.Processors.Convolution;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing namespace SixLabors.ImageSharp.Processing
{ {
/// <summary> /// <summary>
/// Adds Gaussian sharpening extensions to the <see cref="Image{TPixel}"/> type. /// Adds Gaussian sharpening extensions to the <see cref="Image"/> type.
/// </summary> /// </summary>
public static class GaussianSharpenExtensions public static class GaussianSharpenExtensions
{ {
/// <summary> /// <summary>
/// Applies a Gaussian sharpening filter to the image. /// Applies a Gaussian sharpening filter to the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> GaussianSharpen<TPixel>(this IImageProcessingContext<TPixel> source) public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source) =>
where TPixel : struct, IPixel<TPixel> source.ApplyProcessor(new GaussianSharpenProcessor());
=> source.ApplyProcessor(new GaussianSharpenProcessor<TPixel>());
/// <summary> /// <summary>
/// Applies a Gaussian sharpening filter to the image. /// Applies a Gaussian sharpening filter to the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param> /// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> GaussianSharpen<TPixel>(this IImageProcessingContext<TPixel> source, float sigma) public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma) =>
where TPixel : struct, IPixel<TPixel> source.ApplyProcessor(new GaussianSharpenProcessor(sigma));
=> source.ApplyProcessor(new GaussianSharpenProcessor<TPixel>(sigma));
/// <summary> /// <summary>
/// Applies a Gaussian sharpening filter to the image. /// Applies a Gaussian sharpening filter to the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param> /// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle"> /// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter. /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param> /// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> GaussianSharpen<TPixel>(this IImageProcessingContext<TPixel> source, float sigma, Rectangle rectangle) public static IImageProcessingContext GaussianSharpen(
where TPixel : struct, IPixel<TPixel> this IImageProcessingContext source,
=> source.ApplyProcessor(new GaussianSharpenProcessor<TPixel>(sigma), rectangle); float sigma,
Rectangle rectangle) =>
source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle);
} }
} }

93
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs

@ -0,0 +1,93 @@
// // Copyright (c) Six Labors and contributors.
// // Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
internal static class ConvolutionProcessorHelpers
{
/// <summary>
/// Kernel radius is calculated using the minimum viable value.
/// <see cref="http://chemaguerra.com/gaussian-filter-radius/"/>.
/// </summary>
internal static int GetDefaultGaussianRadius(float sigma)
{
return (int)MathF.Ceiling(sigma * 3);
}
/// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary>
/// <returns>The <see cref="DenseMatrix{T}"/>.</returns>
internal static DenseMatrix<float> CreateGaussianBlurKernel(int size, float weight)
{
var kernel = new DenseMatrix<float>(size, 1);
float sum = 0F;
float midpoint = (size - 1) / 2F;
for (int i = 0; i < size; i++)
{
float x = i - midpoint;
float gx = ImageMaths.Gaussian(x, weight);
sum += gx;
kernel[0, i] = gx;
}
// Normalize kernel so that the sum of all weights equals 1
for (int i = 0; i < size; i++)
{
kernel[0, i] /= sum;
}
return kernel;
}
/// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary>
/// <returns>The <see cref="DenseMatrix{T}"/>.</returns>
internal static DenseMatrix<float> CreateGaussianSharpenKernel(int size, float weight)
{
var kernel = new DenseMatrix<float>(size, 1);
float sum = 0;
float midpoint = (size - 1) / 2F;
for (int i = 0; i < size; i++)
{
float x = i - midpoint;
float gx = ImageMaths.Gaussian(x, weight);
sum += gx;
kernel[0, i] = gx;
}
// Invert the kernel for sharpening.
int midpointRounded = (int)midpoint;
for (int i = 0; i < size; i++)
{
if (i == midpointRounded)
{
// Calculate central value
kernel[0, i] = (2F * sum) - kernel[0, i];
}
else
{
// invert value
kernel[0, i] = -kernel[0, i];
}
}
// Normalize kernel so that the sum of all weights equals 1
for (int i = 0; i < size; i++)
{
kernel[0, i] /= sum;
}
return kernel;
}
}
}

16
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs

@ -1,14 +1,13 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
/// Defines a gaussian blur processor with a (Sigma, Radius) pair. /// Defines Gaussian blur by a (Sigma, Radius) pair.
/// </summary> /// </summary>
public class GaussianBlurProcessor : IImageProcessor public class GaussianBlurProcessor : IImageProcessor
{ {
@ -21,7 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="GaussianBlurProcessor"/> class. /// Initializes a new instance of the <see cref="GaussianBlurProcessor"/> class.
/// </summary> /// </summary>
public GaussianBlurProcessor() public GaussianBlurProcessor()
: this(DefaultSigma, CalculateDefaultRadius(DefaultSigma)) : this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma))
{ {
} }
@ -30,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary> /// </summary>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param> /// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
public GaussianBlurProcessor(float sigma) public GaussianBlurProcessor(float sigma)
: this(sigma, CalculateDefaultRadius(sigma)) : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma))
{ {
} }
@ -77,14 +76,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
return new GaussianBlurProcessor<TPixel>(this); return new GaussianBlurProcessor<TPixel>(this);
} }
/// <summary>
/// Kernel radius is calculated using the minimum viable value.
/// <see cref="http://chemaguerra.com/gaussian-filter-radius/"/>.
/// </summary>
private static int CalculateDefaultRadius(float sigma)
{
return (int)MathF.Ceiling(sigma * 3);
}
} }
} }

37
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs

@ -1,5 +1,5 @@
// // Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// // Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Primitives;
@ -14,17 +14,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
internal class GaussianBlurProcessor<TPixel> : ImageProcessor<TPixel> internal class GaussianBlurProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly GaussianBlurProcessor definition;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="GaussianBlurProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param> /// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param>
public GaussianBlurProcessor(GaussianBlurProcessor definition) public GaussianBlurProcessor(GaussianBlurProcessor definition)
{ {
this.definition = definition;
int kernelSize = (definition.Radius * 2) + 1; int kernelSize = (definition.Radius * 2) + 1;
this.KernelX = CreateGaussianKernel(kernelSize, definition.Sigma); this.KernelX = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
this.KernelY = this.KernelX.Transpose(); this.KernelY = this.KernelX.Transpose();
} }
@ -47,33 +44,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
source, source,
sourceRectangle, sourceRectangle,
configuration); configuration);
/// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary>
/// <returns>The <see cref="DenseMatrix{T}"/></returns>
private static DenseMatrix<float> CreateGaussianKernel(int size, float weight)
{
var kernel = new DenseMatrix<float>(size, 1);
float sum = 0F;
float midpoint = (size - 1) / 2F;
for (int i = 0; i < size; i++)
{
float x = i - midpoint;
float gx = ImageMaths.Gaussian(x, weight);
sum += gx;
kernel[0, i] = gx;
}
// Normalize kernel so that the sum of all weights equals 1
for (int i = 0; i < size; i++)
{
kernel[0, i] /= sum;
}
return kernel;
}
} }
} }

101
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs

@ -3,38 +3,38 @@
using System; using System;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
/// <summary> /// <summary>
/// Applies Gaussian sharpening processing to the image. /// Defines Gaussian sharpening by a (Sigma, Radius) pair.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public class GaussianSharpenProcessor : IImageProcessor
internal class GaussianSharpenProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
/// <summary>
/// The default value for <see cref="Sigma"/>.
/// </summary>
public const float DefaultSigma = 3f;
/// <summary> /// <summary>
/// The maximum size of the kernel in either direction. /// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary> /// </summary>
private readonly int kernelSize; public GaussianSharpenProcessor()
: this(DefaultSigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(DefaultSigma))
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary> /// </summary>
/// <param name="sigma"> /// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// The 'sigma' value representing the weight of the sharpening. public GaussianSharpenProcessor(float sigma)
/// </param> : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma))
public GaussianSharpenProcessor(float sigma = 3F)
: this(sigma, (int)MathF.Ceiling(sigma * 3))
{ {
// Kernel radius is calculated using the minimum viable value.
// http://chemaguerra.com/gaussian-filter-radius/
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary> /// </summary>
/// <param name="radius"> /// <param name="radius">
/// The 'radius' value representing the size of the area to sample. /// The 'radius' value representing the size of the area to sample.
@ -45,10 +45,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary> /// </summary>
/// <param name="sigma"> /// <param name="sigma">
/// The 'sigma' value representing the weight of the sharpen. /// The 'sigma' value representing the weight of the blur.
/// </param> /// </param>
/// <param name="radius"> /// <param name="radius">
/// The 'radius' value representing the size of the area to sample. /// The 'radius' value representing the size of the area to sample.
@ -56,10 +56,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </param> /// </param>
public GaussianSharpenProcessor(float sigma, int radius) public GaussianSharpenProcessor(float sigma, int radius)
{ {
this.kernelSize = (radius * 2) + 1;
this.Sigma = sigma; this.Sigma = sigma;
this.KernelX = this.CreateGaussianKernel(); this.Radius = radius;
this.KernelY = this.KernelX.Transpose();
} }
/// <summary> /// <summary>
@ -68,63 +66,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public float Sigma { get; } public float Sigma { get; }
/// <summary> /// <summary>
/// Gets the horizontal gradient operator. /// Gets the radius defining the size of the area to sample.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary> /// </summary>
public DenseMatrix<float> KernelY { get; } public int Radius { get; }
/// <inheritdoc/> public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>()
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) where TPixel : struct, IPixel<TPixel>
=> new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false).Apply(source, sourceRectangle, configuration);
/// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary>
/// <returns>The <see cref="DenseMatrix{T}"/></returns>
private DenseMatrix<float> CreateGaussianKernel()
{ {
int size = this.kernelSize; return new GaussianSharpenProcessor<TPixel>(this);
float weight = this.Sigma;
var kernel = new DenseMatrix<float>(size, 1);
float sum = 0;
float midpoint = (size - 1) / 2F;
for (int i = 0; i < size; i++)
{
float x = i - midpoint;
float gx = ImageMaths.Gaussian(x, weight);
sum += gx;
kernel[0, i] = gx;
}
// Invert the kernel for sharpening.
int midpointRounded = (int)midpoint;
for (int i = 0; i < size; i++)
{
if (i == midpointRounded)
{
// Calculate central value
kernel[0, i] = (2F * sum) - kernel[0, i];
}
else
{
// invert value
kernel[0, i] = -kernel[0, i];
}
}
// Normalize kernel so that the sum of all weights equals 1
for (int i = 0; i < size; i++)
{
kernel[0, i] /= sum;
}
return kernel;
} }
} }
} }

42
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Applies Gaussian sharpening processing to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class GaussianSharpenProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class.
/// </summary>
/// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param>
public GaussianSharpenProcessor(GaussianSharpenProcessor definition)
{
int kernelSize = (definition.Radius * 2) + 1;
this.KernelX = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma);
this.KernelY = this.KernelX.Transpose();
}
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
=> new Convolution2PassProcessor<TPixel>(this.KernelX, this.KernelY, false).Apply(source, sourceRectangle, configuration);
}
}

6
tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
public void GaussianSharpen_GaussianSharpenProcessorDefaultsSet() public void GaussianSharpen_GaussianSharpenProcessorDefaultsSet()
{ {
this.operations.GaussianSharpen(); this.operations.GaussianSharpen();
var processor = this.Verify<GaussianSharpenProcessor<Rgba32>>(); var processor = this.Verify<GaussianSharpenProcessor>();
Assert.Equal(3f, processor.Sigma); Assert.Equal(3f, processor.Sigma);
} }
@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet() public void GaussianSharpen_amount_GaussianSharpenProcessorDefaultsSet()
{ {
this.operations.GaussianSharpen(0.2f); this.operations.GaussianSharpen(0.2f);
var processor = this.Verify<GaussianSharpenProcessor<Rgba32>>(); var processor = this.Verify<GaussianSharpenProcessor>();
Assert.Equal(.2f, processor.Sigma); Assert.Equal(.2f, processor.Sigma);
} }
@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution
public void GaussianSharpen_amount_rect_GaussianSharpenProcessorDefaultsSet() public void GaussianSharpen_amount_rect_GaussianSharpenProcessorDefaultsSet()
{ {
this.operations.GaussianSharpen(0.6f, this.rect); this.operations.GaussianSharpen(0.6f, this.rect);
var processor = this.Verify<GaussianSharpenProcessor<Rgba32>>(this.rect); var processor = this.Verify<GaussianSharpenProcessor>(this.rect);
Assert.Equal(.6f, processor.Sigma); Assert.Equal(.6f, processor.Sigma);
} }

Loading…
Cancel
Save