Browse Source

Use ConvolutionState and Unsafe.Add

pull/2219/head
Ynse Hoornenborg 4 years ago
parent
commit
f97a2cc22e
  1. 21
      src/ImageSharp/Primitives/DenseMatrix{T}.cs
  2. 92
      src/ImageSharp/Processing/Processors/Convolution/Kernel.cs
  3. 46
      src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs
  4. 56
      src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs

21
src/ImageSharp/Primitives/DenseMatrix{T}.cs

@ -96,6 +96,27 @@ namespace SixLabors.ImageSharp
}
}
/// <summary>
/// Initializes a new instance of the <see cref=" DenseMatrix{T}"/> struct.
/// </summary>
/// <param name="columns">The number of columns.</param>
/// <param name="rows">The number of rows.</param>
/// <param name="data">The array to provide access to.</param>
public DenseMatrix(int columns, int rows, Span<T> data)
{
Guard.MustBeGreaterThan(rows, 0, nameof(this.Rows));
Guard.MustBeGreaterThan(columns, 0, nameof(this.Columns));
Guard.IsTrue(rows * columns == data.Length, nameof(data), "Length should be equal to ros * columns");
this.Rows = rows;
this.Columns = columns;
this.Size = new Size(columns, rows);
this.Count = this.Columns * this.Rows;
this.Data = new T[this.Columns * this.Rows];
data.CopyTo(this.Data);
}
/// <summary>
/// Gets a span wrapping the <see cref="Data"/>.
/// </summary>

92
src/ImageSharp/Processing/Processors/Convolution/Kernel.cs

@ -0,0 +1,92 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// A stack only, readonly, kernel matrix that can be indexed without
/// bounds checks when compiled in release mode.
/// </summary>
internal readonly ref struct Kernel<T>
where T : struct, IEquatable<T>
{
private readonly Span<T> values;
public Kernel(DenseMatrix<T> matrix)
{
this.Columns = matrix.Columns;
this.Rows = matrix.Rows;
this.values = matrix.Span;
}
public int Columns
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
public int Rows
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
public ReadOnlySpan<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.values;
}
}
public T this[int row, int column]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
this.CheckCoordinates(row, column);
ref T vBase = ref MemoryMarshal.GetReference(this.values);
return Unsafe.Add(ref vBase, (row * this.Columns) + column);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetValue(int row, int column, T value)
{
this.SetValue((row * this.Columns) + column, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetValue(int index, T value)
{
ref T vBase = ref MemoryMarshal.GetReference(this.values);
Unsafe.Add(ref vBase, index) = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
this.values.Clear();
}
[Conditional("DEBUG")]
private void CheckCoordinates(int row, int column)
{
if (row < 0 || row >= this.Rows)
{
throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outside the matrix bounds.");
}
if (column < 0 || column >= this.Columns)
{
throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outside the matrix bounds.");
}
}
}
}

46
src/ImageSharp/Processing/Processors/Convolution/MedianConvolutionState.cs

@ -0,0 +1,46 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// A stack only struct used for reducing reference indirection during convolution operations.
/// </summary>
internal readonly ref struct MedianConvolutionState
{
private readonly Span<int> rowOffsetMap;
private readonly Span<int> columnOffsetMap;
private readonly int kernelHeight;
private readonly int kernelWidth;
public MedianConvolutionState(
in DenseMatrix<Vector4> kernel,
KernelSamplingMap map)
{
this.Kernel = new Kernel<Vector4>(kernel);
this.kernelHeight = kernel.Rows;
this.kernelWidth = kernel.Columns;
this.rowOffsetMap = map.GetRowOffsetSpan();
this.columnOffsetMap = map.GetColumnOffsetSpan();
}
public readonly Kernel<Vector4> Kernel
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ref int GetSampleRow(int row)
=> ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ref int GetSampleColumn(int column)
=> ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth);
}
}

56
src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs

@ -59,31 +59,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Span<float> yChannel = channelBuffer.Slice(this.yChannelStart, kernelCount);
Span<float> zChannel = channelBuffer.Slice(this.zChannelStart, kernelCount);
Span<int> xOffsets = this.map.GetColumnOffsetSpan();
Span<int> yOffsets = this.map.GetRowOffsetSpan();
int baseXOffsetIndex = 0;
int baseYOffsetIndex = (y - this.bounds.Top) * this.kernelSize;
DenseMatrix<Vector4> kernel = new DenseMatrix<Vector4>(this.kernelSize, this.kernelSize, kernelBuffer);
int row = y - this.bounds.Y;
MedianConvolutionState state = new MedianConvolutionState(in kernel, this.map);
ref int sampleRowBase = ref state.GetSampleRow(row);
ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer);
if (this.preserveAlpha)
{
for (int x = 0; x < boundsWidth; x++)
{
int index = 0;
for (int kY = 0; kY < this.kernelSize; kY++)
ref int sampleColumnBase = ref state.GetSampleColumn(x);
ref Vector4 target = ref Unsafe.Add(ref targetBase, x);
for (int kY = 0; kY < state.Kernel.Rows; kY++)
{
int currentY = yOffsets[baseYOffsetIndex + kY];
Span<TPixel> row = this.sourcePixels.DangerousGetRowSpan(currentY);
for (int kX = 0; kX < this.kernelSize; kX++)
int currentYIndex = Unsafe.Add(ref sampleRowBase, kY);
Span<TPixel> sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth);
ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourceRow);
for (int kX = 0; kX < state.Kernel.Columns; kX++)
{
int currentX = xOffsets[baseXOffsetIndex + kX];
TPixel pixel = row[currentX];
kernelBuffer[index] = pixel.ToVector4();
int currentXIndex = Unsafe.Add(ref sampleColumnBase, kX) - boundsX;
TPixel pixel = Unsafe.Add(ref sourceRowBase, currentXIndex);
state.Kernel.SetValue(index, pixel.ToVector4());
index++;
}
}
targetBuffer[x] = this.FindMedian3(kernelBuffer, xChannel, yChannel, zChannel, kernelCount);
baseXOffsetIndex += this.kernelSize;
target = this.FindMedian3(state.Kernel.Span, xChannel, yChannel, zChannel, kernelCount);
state.Kernel.Clear();
}
}
else
@ -92,21 +97,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
for (int x = 0; x < boundsWidth; x++)
{
int index = 0;
for (int kY = 0; kY < this.kernelSize; kY++)
ref int sampleColumnBase = ref state.GetSampleColumn(x);
ref Vector4 target = ref Unsafe.Add(ref targetBase, x);
for (int kY = 0; kY < state.Kernel.Rows; kY++)
{
int j = yOffsets[baseYOffsetIndex + kY];
Span<TPixel> row = this.sourcePixels.DangerousGetRowSpan(j);
for (int kX = 0; kX < this.kernelSize; kX++)
int currentYIndex = Unsafe.Add(ref sampleRowBase, kY);
Span<TPixel> sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth);
ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourceRow);
for (int kX = 0; kX < state.Kernel.Columns; kX++)
{
int currentX = xOffsets[baseXOffsetIndex + kX];
TPixel pixel = row[currentX];
kernelBuffer[index] = pixel.ToVector4();
int currentXIndex = Unsafe.Add(ref sampleColumnBase, kX) - boundsX;
TPixel pixel = Unsafe.Add(ref sourceRowBase, currentXIndex);
state.Kernel.SetValue(index, pixel.ToVector4());
index++;
}
}
targetBuffer[x] = this.FindMedian4(kernelBuffer, xChannel, yChannel, zChannel, wChannel, kernelCount);
baseXOffsetIndex += this.kernelSize;
target = this.FindMedian4(state.Kernel.Span, xChannel, yChannel, zChannel, wChannel, kernelCount);
state.Kernel.Clear();
}
}
@ -115,7 +123,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Vector4 FindMedian3(Span<Vector4> kernelSpan, Span<float> xChannel, Span<float> yChannel, Span<float> zChannel, int stride)
private Vector4 FindMedian3(ReadOnlySpan<Vector4> kernelSpan, Span<float> xChannel, Span<float> yChannel, Span<float> zChannel, int stride)
{
int halfLength = (kernelSpan.Length + 1) >> 1;
@ -138,7 +146,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Vector4 FindMedian4(Span<Vector4> kernelSpan, Span<float> xChannel, Span<float> yChannel, Span<float> zChannel, Span<float> wChannel, int stride)
private Vector4 FindMedian4(ReadOnlySpan<Vector4> kernelSpan, Span<float> xChannel, Span<float> yChannel, Span<float> zChannel, Span<float> wChannel, int stride)
{
int halfLength = (kernelSpan.Length + 1) >> 1;

Loading…
Cancel
Save