Browse Source

Remove transposed 1D kernels, switch to float[] type

js/color-alpha-handling
Sergio Pedri 6 years ago
parent
commit
e8bf265468
  1. 23
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
  2. 55
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  3. 28
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs
  4. 14
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
  5. 14
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs

23
src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// 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;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution namespace SixLabors.ImageSharp.Processing.Processors.Convolution
@ -23,24 +24,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
int kernelSize = (definition.Radius * 2) + 1; int kernelSize = (definition.Radius * 2) + 1;
this.KernelX = CreateBoxKernel(kernelSize); this.Kernel = CreateBoxKernel(kernelSize);
this.KernelY = this.KernelX.Transpose();
} }
/// <summary> /// <summary>
/// Gets the horizontal gradient operator. /// Gets the 1D convolution kernel.
/// </summary> /// </summary>
public DenseMatrix<float> KernelX { get; } public float[] Kernel { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle);
processor.Apply(source); processor.Apply(source);
} }
@ -50,10 +45,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary> /// </summary>
/// <param name="kernelSize">The maximum size of the kernel in either direction.</param> /// <param name="kernelSize">The maximum size of the kernel in either direction.</param>
/// <returns>The <see cref="DenseMatrix{T}"/>.</returns> /// <returns>The <see cref="DenseMatrix{T}"/>.</returns>
private static DenseMatrix<float> CreateBoxKernel(int kernelSize) private static float[] CreateBoxKernel(int kernelSize)
{ {
var kernel = new DenseMatrix<float>(kernelSize, 1); var kernel = new float[kernelSize];
kernel.Fill(1F / kernelSize);
kernel.AsSpan().Fill(1F / kernelSize);
return kernel; return kernel;
} }
} }

55
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -22,34 +22,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="Convolution2PassProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="Convolution2PassProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="kernelX">The horizontal gradient operator.</param> /// <param name="kernel">The 1D convolution kernel.</param>
/// <param name="kernelY">The vertical gradient operator.</param>
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param> /// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param> /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param> /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public Convolution2PassProcessor( public Convolution2PassProcessor(
Configuration configuration, Configuration configuration,
in DenseMatrix<float> kernelX, float[] kernel,
in DenseMatrix<float> kernelY,
bool preserveAlpha, bool preserveAlpha,
Image<TPixel> source, Image<TPixel> source,
Rectangle sourceRectangle) Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
this.KernelX = kernelX; this.Kernel = kernel;
this.KernelY = kernelY;
this.PreserveAlpha = preserveAlpha; this.PreserveAlpha = preserveAlpha;
} }
/// <summary> /// <summary>
/// Gets the horizontal convolution kernel. /// Gets the convolution kernel.
/// </summary> /// </summary>
public DenseMatrix<float> KernelX { get; } public float[] Kernel { get; }
/// <summary>
/// Gets the vertical convolution kernel.
/// </summary>
public DenseMatrix<float> KernelY { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels. /// Gets a value indicating whether the convolution filter is applied to alpha as well as the color channels.
@ -71,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur. // the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur.
using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator); using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator);
mapXY.BuildSamplingOffsetMap(this.KernelY.Rows, this.KernelX.Columns, interest); mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest);
// Horizontal convolution // Horizontal convolution
var horizontalOperation = new HorizontalConvolutionRowOperation( var horizontalOperation = new HorizontalConvolutionRowOperation(
@ -79,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
firstPassPixels, firstPassPixels,
source.PixelBuffer, source.PixelBuffer,
mapXY, mapXY,
this.KernelX, this.Kernel,
this.Configuration, this.Configuration,
this.PreserveAlpha); this.PreserveAlpha);
@ -94,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
source.PixelBuffer, source.PixelBuffer,
firstPassPixels, firstPassPixels,
mapXY, mapXY,
this.KernelY, this.Kernel,
this.Configuration, this.Configuration,
this.PreserveAlpha); this.PreserveAlpha);
@ -113,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly Buffer2D<TPixel> targetPixels; private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> sourcePixels; private readonly Buffer2D<TPixel> sourcePixels;
private readonly KernelSamplingMap map; private readonly KernelSamplingMap map;
private readonly DenseMatrix<float> kernelMatrix; private readonly float[] kernel;
private readonly Configuration configuration; private readonly Configuration configuration;
private readonly bool preserveAlpha; private readonly bool preserveAlpha;
@ -123,7 +115,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Buffer2D<TPixel> targetPixels, Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels, Buffer2D<TPixel> sourcePixels,
KernelSamplingMap map, KernelSamplingMap map,
DenseMatrix<float> kernelMatrix, float[] kernel,
Configuration configuration, Configuration configuration,
bool preserveAlpha) bool preserveAlpha)
{ {
@ -131,7 +123,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.targetPixels = targetPixels; this.targetPixels = targetPixels;
this.sourcePixels = sourcePixels; this.sourcePixels = sourcePixels;
this.map = map; this.map = map;
this.kernelMatrix = kernelMatrix; this.kernel = kernel;
this.configuration = configuration; this.configuration = configuration;
this.preserveAlpha = preserveAlpha; this.preserveAlpha = preserveAlpha;
} }
@ -156,7 +148,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Span is 2x bounds. // Span is 2x bounds.
int boundsX = this.bounds.X; int boundsX = this.bounds.X;
int boundsWidth = this.bounds.Width; int boundsWidth = this.bounds.Width;
int kernelSize = this.kernelMatrix.Columns; int kernelSize = this.kernel.Length;
Span<Vector4> sourceBuffer = span.Slice(0, this.bounds.Width); Span<Vector4> sourceBuffer = span.Slice(0, this.bounds.Width);
Span<Vector4> targetBuffer = span.Slice(this.bounds.Width); Span<Vector4> targetBuffer = span.Slice(this.bounds.Width);
@ -170,7 +162,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer);
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer);
ref float kernelBase = ref this.kernelMatrix[0, 0]; ref float kernelBase = ref this.kernel[0];
ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan());
for (int x = 0; x < sourceBuffer.Length; x++) for (int x = 0; x < sourceBuffer.Length; x++)
@ -210,7 +202,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Span is 2x bounds. // Span is 2x bounds.
int boundsX = this.bounds.X; int boundsX = this.bounds.X;
int boundsWidth = this.bounds.Width; int boundsWidth = this.bounds.Width;
int kernelSize = this.kernelMatrix.Columns; int kernelSize = this.kernel.Length;
Span<Vector4> sourceBuffer = span.Slice(0, this.bounds.Width); Span<Vector4> sourceBuffer = span.Slice(0, this.bounds.Width);
Span<Vector4> targetBuffer = span.Slice(this.bounds.Width); Span<Vector4> targetBuffer = span.Slice(this.bounds.Width);
@ -226,7 +218,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Numerics.Premultiply(sourceBuffer); Numerics.Premultiply(sourceBuffer);
ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer);
ref float kernelBase = ref this.kernelMatrix[0, 0]; ref float kernelBase = ref this.kernel[0];
ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan()); ref int sampleColumnBase = ref MemoryMarshal.GetReference(this.map.GetColumnOffsetSpan());
for (int x = 0; x < sourceBuffer.Length; x++) for (int x = 0; x < sourceBuffer.Length; x++)
@ -261,7 +253,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly Buffer2D<TPixel> targetPixels; private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> sourcePixels; private readonly Buffer2D<TPixel> sourcePixels;
private readonly KernelSamplingMap map; private readonly KernelSamplingMap map;
private readonly DenseMatrix<float> kernelMatrix; private readonly float[] kernel;
private readonly Configuration configuration; private readonly Configuration configuration;
private readonly bool preserveAlpha; private readonly bool preserveAlpha;
@ -271,7 +263,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Buffer2D<TPixel> targetPixels, Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels, Buffer2D<TPixel> sourcePixels,
KernelSamplingMap map, KernelSamplingMap map,
DenseMatrix<float> kernelMatrix, float[] kernel,
Configuration configuration, Configuration configuration,
bool preserveAlpha) bool preserveAlpha)
{ {
@ -279,7 +271,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.targetPixels = targetPixels; this.targetPixels = targetPixels;
this.sourcePixels = sourcePixels; this.sourcePixels = sourcePixels;
this.map = map; this.map = map;
this.kernelMatrix = kernelMatrix; this.kernel = kernel;
this.configuration = configuration; this.configuration = configuration;
this.preserveAlpha = preserveAlpha; this.preserveAlpha = preserveAlpha;
} }
@ -304,7 +296,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Span is 2x bounds. // Span is 2x bounds.
int boundsX = this.bounds.X; int boundsX = this.bounds.X;
int boundsWidth = this.bounds.Width; int boundsWidth = this.bounds.Width;
int kernelSize = this.kernelMatrix.Rows; int kernelSize = this.kernel.Length;
Span<Vector4> sourceBuffer = span.Slice(0, this.bounds.Width); Span<Vector4> sourceBuffer = span.Slice(0, this.bounds.Width);
Span<Vector4> targetBuffer = span.Slice(this.bounds.Width); Span<Vector4> targetBuffer = span.Slice(this.bounds.Width);
@ -315,7 +307,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
targetBuffer.Clear(); targetBuffer.Clear();
ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer);
ref float kernelBase = ref this.kernelMatrix[0, 0]; ref float kernelBase = ref this.kernel[0];
Span<TPixel> sourceRow; Span<TPixel> sourceRow;
for (int kY = 0; kY < kernelSize; kY++) for (int kY = 0; kY < kernelSize; kY++)
@ -358,19 +350,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Span is 2x bounds. // Span is 2x bounds.
int boundsX = this.bounds.X; int boundsX = this.bounds.X;
int boundsWidth = this.bounds.Width; int boundsWidth = this.bounds.Width;
int kernelSize = this.kernelMatrix.Rows; int kernelSize = this.kernel.Length;
Span<Vector4> sourceBuffer = span.Slice(0, this.bounds.Width); Span<Vector4> sourceBuffer = span.Slice(0, this.bounds.Width);
Span<Vector4> targetBuffer = span.Slice(this.bounds.Width); Span<Vector4> targetBuffer = span.Slice(this.bounds.Width);
var state = new ConvolutionState(in this.kernelMatrix, this.map); ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (y - this.bounds.Y) * kernelSize);
ref int sampleRowBase = ref state.GetSampleRow(y - this.bounds.Y);
// Clear the target buffer for each row run. // Clear the target buffer for each row run.
targetBuffer.Clear(); targetBuffer.Clear();
ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer);
ref float kernelBase = ref this.kernelMatrix[0, 0]; ref float kernelBase = ref this.kernel[0];
for (int kY = 0; kY < kernelSize; kY++) for (int kY = 0; kY < kernelSize; kY++)
{ {

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

@ -12,17 +12,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// See <see href="http://chemaguerra.com/gaussian-filter-radius/"/>. /// See <see href="http://chemaguerra.com/gaussian-filter-radius/"/>.
/// </summary> /// </summary>
internal static int GetDefaultGaussianRadius(float sigma) internal static int GetDefaultGaussianRadius(float sigma)
{ => (int)MathF.Ceiling(sigma * 3);
return (int)MathF.Ceiling(sigma * 3);
}
/// <summary> /// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function. /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function.
/// </summary> /// </summary>
/// <returns>The <see cref="DenseMatrix{T}"/>.</returns> /// <returns>The convolution kernel.</returns>
internal static DenseMatrix<float> CreateGaussianBlurKernel(int size, float weight) internal static float[] CreateGaussianBlurKernel(int size, float weight)
{ {
var kernel = new DenseMatrix<float>(size, 1); var kernel = new float[size];
float sum = 0F; float sum = 0F;
float midpoint = (size - 1) / 2F; float midpoint = (size - 1) / 2F;
@ -32,13 +30,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
float x = i - midpoint; float x = i - midpoint;
float gx = Numerics.Gaussian(x, weight); float gx = Numerics.Gaussian(x, weight);
sum += gx; sum += gx;
kernel[0, i] = gx; kernel[i] = gx;
} }
// Normalize kernel so that the sum of all weights equals 1 // Normalize kernel so that the sum of all weights equals 1
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
kernel[0, i] /= sum; kernel[i] /= sum;
} }
return kernel; return kernel;
@ -47,10 +45,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <summary> /// <summary>
/// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function
/// </summary> /// </summary>
/// <returns>The <see cref="DenseMatrix{T}"/>.</returns> /// <returns>The convolution kernel.</returns>
internal static DenseMatrix<float> CreateGaussianSharpenKernel(int size, float weight) internal static float[] CreateGaussianSharpenKernel(int size, float weight)
{ {
var kernel = new DenseMatrix<float>(size, 1); var kernel = new float[size];
float sum = 0; float sum = 0;
@ -60,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
float x = i - midpoint; float x = i - midpoint;
float gx = Numerics.Gaussian(x, weight); float gx = Numerics.Gaussian(x, weight);
sum += gx; sum += gx;
kernel[0, i] = gx; kernel[i] = gx;
} }
// Invert the kernel for sharpening. // Invert the kernel for sharpening.
@ -70,19 +68,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
if (i == midpointRounded) if (i == midpointRounded)
{ {
// Calculate central value // Calculate central value
kernel[0, i] = (2F * sum) - kernel[0, i]; kernel[i] = (2F * sum) - kernel[i];
} }
else else
{ {
// invert value // invert value
kernel[0, i] = -kernel[0, i]; kernel[i] = -kernel[i];
} }
} }
// Normalize kernel so that the sum of all weights equals 1 // Normalize kernel so that the sum of all weights equals 1
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
kernel[0, i] /= sum; kernel[i] /= sum;
} }
return kernel; return kernel;

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

@ -27,24 +27,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
int kernelSize = (definition.Radius * 2) + 1; int kernelSize = (definition.Radius * 2) + 1;
this.KernelX = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
this.KernelY = this.KernelX.Transpose();
} }
/// <summary> /// <summary>
/// Gets the horizontal gradient operator. /// Gets the 1D convolution kernel.
/// </summary> /// </summary>
public DenseMatrix<float> KernelX { get; } public float[] Kernel { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle);
processor.Apply(source); processor.Apply(source);
} }

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

@ -27,24 +27,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
int kernelSize = (definition.Radius * 2) + 1; int kernelSize = (definition.Radius * 2) + 1;
this.KernelX = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma); this.Kernel = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma);
this.KernelY = this.KernelX.Transpose();
} }
/// <summary> /// <summary>
/// Gets the horizontal gradient operator. /// Gets the 1D convolution kernel.
/// </summary> /// </summary>
public DenseMatrix<float> KernelX { get; } public float[] Kernel { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle);
processor.Apply(source); processor.Apply(source);
} }

Loading…
Cancel
Save