Browse Source

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

js/color-alpha-handling
Sergio Pedri 5 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.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
@ -23,24 +24,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
: base(configuration, source, sourceRectangle)
{
int kernelSize = (definition.Radius * 2) + 1;
this.KernelX = CreateBoxKernel(kernelSize);
this.KernelY = this.KernelX.Transpose();
this.Kernel = CreateBoxKernel(kernelSize);
}
/// <summary>
/// Gets the horizontal gradient operator.
/// Gets the 1D convolution kernel.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
public float[] Kernel { get; }
/// <inheritdoc/>
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);
}
@ -50,10 +45,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
/// <param name="kernelSize">The maximum size of the kernel in either direction.</param>
/// <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);
kernel.Fill(1F / kernelSize);
var kernel = new float[kernelSize];
kernel.AsSpan().Fill(1F / kernelSize);
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.
/// </summary>
/// <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="kernelY">The vertical gradient operator.</param>
/// <param name="kernel">The 1D convolution kernel.</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="sourceRectangle">The source area to process for the current processor instance.</param>
public Convolution2PassProcessor(
Configuration configuration,
in DenseMatrix<float> kernelX,
in DenseMatrix<float> kernelY,
float[] kernel,
bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.KernelX = kernelX;
this.KernelY = kernelY;
this.Kernel = kernel;
this.PreserveAlpha = preserveAlpha;
}
/// <summary>
/// Gets the horizontal convolution kernel.
/// Gets the convolution kernel.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical convolution kernel.
/// </summary>
public DenseMatrix<float> KernelY { get; }
public float[] Kernel { get; }
/// <summary>
/// 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.
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
var horizontalOperation = new HorizontalConvolutionRowOperation(
@ -79,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
firstPassPixels,
source.PixelBuffer,
mapXY,
this.KernelX,
this.Kernel,
this.Configuration,
this.PreserveAlpha);
@ -94,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
source.PixelBuffer,
firstPassPixels,
mapXY,
this.KernelY,
this.Kernel,
this.Configuration,
this.PreserveAlpha);
@ -113,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> sourcePixels;
private readonly KernelSamplingMap map;
private readonly DenseMatrix<float> kernelMatrix;
private readonly float[] kernel;
private readonly Configuration configuration;
private readonly bool preserveAlpha;
@ -123,7 +115,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
KernelSamplingMap map,
DenseMatrix<float> kernelMatrix,
float[] kernel,
Configuration configuration,
bool preserveAlpha)
{
@ -131,7 +123,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.targetPixels = targetPixels;
this.sourcePixels = sourcePixels;
this.map = map;
this.kernelMatrix = kernelMatrix;
this.kernel = kernel;
this.configuration = configuration;
this.preserveAlpha = preserveAlpha;
}
@ -156,7 +148,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Span is 2x bounds.
int boundsX = this.bounds.X;
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> targetBuffer = span.Slice(this.bounds.Width);
@ -170,7 +162,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, sourceRow, 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());
for (int x = 0; x < sourceBuffer.Length; x++)
@ -210,7 +202,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Span is 2x bounds.
int boundsX = this.bounds.X;
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> targetBuffer = span.Slice(this.bounds.Width);
@ -226,7 +218,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Numerics.Premultiply(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());
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> sourcePixels;
private readonly KernelSamplingMap map;
private readonly DenseMatrix<float> kernelMatrix;
private readonly float[] kernel;
private readonly Configuration configuration;
private readonly bool preserveAlpha;
@ -271,7 +263,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
KernelSamplingMap map,
DenseMatrix<float> kernelMatrix,
float[] kernel,
Configuration configuration,
bool preserveAlpha)
{
@ -279,7 +271,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.targetPixels = targetPixels;
this.sourcePixels = sourcePixels;
this.map = map;
this.kernelMatrix = kernelMatrix;
this.kernel = kernel;
this.configuration = configuration;
this.preserveAlpha = preserveAlpha;
}
@ -304,7 +296,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Span is 2x bounds.
int boundsX = this.bounds.X;
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> targetBuffer = span.Slice(this.bounds.Width);
@ -315,7 +307,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
targetBuffer.Clear();
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;
for (int kY = 0; kY < kernelSize; kY++)
@ -358,19 +350,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// Span is 2x bounds.
int boundsX = this.bounds.X;
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> targetBuffer = span.Slice(this.bounds.Width);
var state = new ConvolutionState(in this.kernelMatrix, this.map);
ref int sampleRowBase = ref state.GetSampleRow(y - this.bounds.Y);
ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (y - this.bounds.Y) * kernelSize);
// Clear the target buffer for each row run.
targetBuffer.Clear();
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++)
{

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/"/>.
/// </summary>
internal static int GetDefaultGaussianRadius(float sigma)
{
return (int)MathF.Ceiling(sigma * 3);
}
=> (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)
/// <returns>The convolution kernel.</returns>
internal static float[] CreateGaussianBlurKernel(int size, float weight)
{
var kernel = new DenseMatrix<float>(size, 1);
var kernel = new float[size];
float sum = 0F;
float midpoint = (size - 1) / 2F;
@ -32,13 +30,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
float x = i - midpoint;
float gx = Numerics.Gaussian(x, weight);
sum += gx;
kernel[0, i] = gx;
kernel[i] = gx;
}
// Normalize kernel so that the sum of all weights equals 1
for (int i = 0; i < size; i++)
{
kernel[0, i] /= sum;
kernel[i] /= sum;
}
return kernel;
@ -47,10 +45,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <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)
/// <returns>The convolution kernel.</returns>
internal static float[] CreateGaussianSharpenKernel(int size, float weight)
{
var kernel = new DenseMatrix<float>(size, 1);
var kernel = new float[size];
float sum = 0;
@ -60,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
float x = i - midpoint;
float gx = Numerics.Gaussian(x, weight);
sum += gx;
kernel[0, i] = gx;
kernel[i] = gx;
}
// Invert the kernel for sharpening.
@ -70,19 +68,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
if (i == midpointRounded)
{
// Calculate central value
kernel[0, i] = (2F * sum) - kernel[0, i];
kernel[i] = (2F * sum) - kernel[i];
}
else
{
// invert value
kernel[0, i] = -kernel[0, i];
kernel[i] = -kernel[i];
}
}
// Normalize kernel so that the sum of all weights equals 1
for (int i = 0; i < size; i++)
{
kernel[0, i] /= sum;
kernel[i] /= sum;
}
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)
{
int kernelSize = (definition.Radius * 2) + 1;
this.KernelX = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
this.KernelY = this.KernelX.Transpose();
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
}
/// <summary>
/// Gets the horizontal gradient operator.
/// Gets the 1D convolution kernel.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
public float[] Kernel { get; }
/// <inheritdoc/>
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);
}

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

@ -27,24 +27,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
: base(configuration, source, sourceRectangle)
{
int kernelSize = (definition.Radius * 2) + 1;
this.KernelX = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma);
this.KernelY = this.KernelX.Transpose();
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma);
}
/// <summary>
/// Gets the horizontal gradient operator.
/// Gets the 1D convolution kernel.
/// </summary>
public DenseMatrix<float> KernelX { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
public DenseMatrix<float> KernelY { get; }
public float[] Kernel { get; }
/// <inheritdoc/>
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);
}

Loading…
Cancel
Save