Browse Source

First working no-clamp version for 2 pass convolution

js/color-alpha-handling
James Jackson-South 5 years ago
parent
commit
d0306a2ab7
  1. 3
      .gitattributes
  2. 58
      src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
  3. 86
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  4. 53
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
  5. 91
      src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs

3
.gitattributes

@ -80,8 +80,11 @@
*.pvr binary
*.snk binary
*.tga binary
*.tif binary
*.tiff binary
*.ttc binary
*.ttf binary
*.wbmp binary
*.webp binary
*.woff binary
*.woff2 binary

58
src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs

@ -6,6 +6,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
namespace SixLabors.ImageSharp
{
@ -156,38 +157,32 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="matrix">The dense matrix.</param>
/// <param name="yOffsetSpan">The span containing precalculated kernel y-offsets.</param>
/// <param name="xOffsetSpan">The span containing precalculated kernel x-offsets.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRowRef">The target row base reference.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="minRow">The minimum working area row.</param>
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="minColumn">The minimum working area column.</param>
/// <param name="maxColumn">The maximum working area column.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Convolve3<TPixel>(
in DenseMatrix<float> matrix,
Span<int> yOffsetSpan,
Span<int> xOffsetSpan,
Buffer2D<TPixel> sourcePixels,
ref Vector4 targetRowRef,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn)
int column)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 vector = default;
ConvolveImpl(
in matrix,
yOffsetSpan,
xOffsetSpan,
sourcePixels,
row,
column,
minRow,
maxRow,
minColumn,
maxColumn,
ref vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
@ -203,38 +198,32 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="matrix">The dense matrix.</param>
/// <param name="yOffsetSpan">The span containing precalculated kernel y-offsets.</param>
/// <param name="xOffsetSpan">The span containing precalculated kernel x-offsets.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRowRef">The target row base reference.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="minRow">The minimum working area row.</param>
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="minColumn">The minimum working area column.</param>
/// <param name="maxColumn">The maximum working area column.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Convolve4<TPixel>(
in DenseMatrix<float> matrix,
Span<int> yOffsetSpan,
Span<int> xOffsetSpan,
Buffer2D<TPixel> sourcePixels,
ref Vector4 targetRowRef,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn)
int column)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 vector = default;
ConvolveImpl(
in matrix,
yOffsetSpan,
xOffsetSpan,
sourcePixels,
row,
column,
minRow,
maxRow,
minColumn,
maxColumn,
ref vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
@ -245,33 +234,28 @@ namespace SixLabors.ImageSharp
[MethodImpl(InliningOptions.ShortMethod)]
private static void ConvolveImpl<TPixel>(
in DenseMatrix<float> matrix,
Span<int> yOffsetSpan,
Span<int> xOffsetSpan,
Buffer2D<TPixel> sourcePixels,
int row,
int column,
int minRow,
int maxRow,
int minColumn,
int maxColumn,
ref Vector4 vector)
ref Vector4 targetVector)
where TPixel : unmanaged, IPixel<TPixel>
{
int matrixHeight = matrix.Rows;
int matrixWidth = matrix.Columns;
int radiusY = matrixHeight >> 1;
int radiusX = matrixWidth >> 1;
int sourceOffsetColumnBase = column + minColumn;
for (int y = 0; y < matrixHeight; y++)
{
int offsetY = Numerics.Clamp(row + y - radiusY, minRow, maxRow);
int offsetY = yOffsetSpan[(row * matrixHeight) + y];
Span<TPixel> sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
for (int x = 0; x < matrixWidth; x++)
{
int offsetX = Numerics.Clamp(sourceOffsetColumnBase + x - radiusX, minColumn, maxColumn);
int offsetX = xOffsetSpan[(column * matrixWidth) + x];
var currentColor = sourceRowSpan[offsetX].ToVector4();
Numerics.Premultiply(ref currentColor);
vector += matrix[y, x] * currentColor;
targetVector += matrix[y, x] * currentColor;
}
}
}

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

@ -63,19 +63,45 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
// Horizontal convolution
var horizontalOperation = new RowOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
this.Configuration,
interest,
in horizontalOperation);
// Vertical convolution
var verticalOperation = new RowOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
this.Configuration,
interest,
in verticalOperation);
using (var mapX = new KernelOffsetMap(this.Configuration.MemoryAllocator))
{
mapX.BuildOffsetMap(this.KernelX, interest);
// Horizontal convolution
var horizontalOperation = new RowOperation(
interest,
firstPassPixels,
source.PixelBuffer,
mapX,
this.KernelX,
this.Configuration,
this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
this.Configuration,
interest,
in horizontalOperation);
}
using (var mapY = new KernelOffsetMap(this.Configuration.MemoryAllocator))
{
mapY.BuildOffsetMap(this.KernelY, interest);
// Vertical convolution
var verticalOperation = new RowOperation(
interest,
source.PixelBuffer,
firstPassPixels,
mapY,
this.KernelY,
this.Configuration,
this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
this.Configuration,
interest,
in verticalOperation);
}
}
/// <summary>
@ -86,6 +112,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly Rectangle bounds;
private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> sourcePixels;
private readonly KernelOffsetMap map;
private readonly DenseMatrix<float> kernel;
private readonly Configuration configuration;
private readonly bool preserveAlpha;
@ -95,6 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
KernelOffsetMap map,
DenseMatrix<float> kernel,
Configuration configuration,
bool preserveAlpha)
@ -102,6 +130,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.bounds = bounds;
this.targetPixels = targetPixels;
this.sourcePixels = sourcePixels;
this.map = map;
this.kernel = kernel;
this.configuration = configuration;
this.preserveAlpha = preserveAlpha;
@ -112,43 +141,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public void Invoke(int y, Span<Vector4> span)
{
ref Vector4 spanRef = ref MemoryMarshal.GetReference(span);
int maxY = this.bounds.Bottom - 1;
int maxX = this.bounds.Right - 1;
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
Span<int> yOffsets = this.map.GetYOffsetSpan();
Span<int> xOffsets = this.map.GetXOffsetSpan();
int row = y - this.bounds.Y;
if (this.preserveAlpha)
{
for (int x = 0; x < this.bounds.Width; x++)
for (int column = 0; column < this.bounds.Width; column++)
{
DenseMatrixUtils.Convolve3(
in this.kernel,
yOffsets,
xOffsets,
this.sourcePixels,
ref spanRef,
y,
x,
this.bounds.Y,
maxY,
this.bounds.X,
maxX);
row,
column);
}
}
else
{
for (int x = 0; x < this.bounds.Width; x++)
for (int column = 0; column < this.bounds.Width; column++)
{
DenseMatrixUtils.Convolve4(
in this.kernel,
yOffsets,
xOffsets,
this.sourcePixels,
ref spanRef,
y,
x,
this.bounds.Y,
maxY,
this.bounds.X,
maxX);
row,
column);
}
}

53
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs

@ -51,16 +51,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
using Buffer2D<TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size());
MemoryAllocator allocator = this.Configuration.MemoryAllocator;
using Buffer2D<TPixel> targetPixels = allocator.Allocate2D<TPixel>(source.Size());
source.CopyTo(targetPixels);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
this.Configuration,
interest,
in operation);
using (var map = new KernelOffsetMap(allocator))
{
map.BuildOffsetMap(this.KernelXY, interest);
var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
this.Configuration,
interest,
in operation);
}
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
@ -71,10 +77,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly struct RowOperation : IRowOperation<Vector4>
{
private readonly Rectangle bounds;
private readonly int maxY;
private readonly int maxX;
private readonly Buffer2D<TPixel> targetPixels;
private readonly Buffer2D<TPixel> sourcePixels;
private readonly KernelOffsetMap map;
private readonly DenseMatrix<float> kernel;
private readonly Configuration configuration;
private readonly bool preserveAlpha;
@ -84,15 +89,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Rectangle bounds,
Buffer2D<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
KernelOffsetMap map,
DenseMatrix<float> kernel,
Configuration configuration,
bool preserveAlpha)
{
this.bounds = bounds;
this.maxY = this.bounds.Bottom - 1;
this.maxX = this.bounds.Right - 1;
this.targetPixels = targetPixels;
this.sourcePixels = sourcePixels;
this.map = map;
this.kernel = kernel;
this.configuration = configuration;
this.preserveAlpha = preserveAlpha;
@ -103,40 +108,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public void Invoke(int y, Span<Vector4> span)
{
ref Vector4 spanRef = ref MemoryMarshal.GetReference(span);
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
Span<int> yOffsetSpan = this.map.GetYOffsetSpan();
Span<int> xOffsetSpan = this.map.GetXOffsetSpan();
int row = y - this.bounds.Y;
if (this.preserveAlpha)
{
for (int x = 0; x < this.bounds.Width; x++)
for (int column = 0; column < this.bounds.Width; column++)
{
DenseMatrixUtils.Convolve3(
in this.kernel,
yOffsetSpan,
xOffsetSpan,
this.sourcePixels,
ref spanRef,
y,
x,
this.bounds.Y,
this.maxY,
this.bounds.X,
this.maxX);
row,
column);
}
}
else
{
for (int x = 0; x < this.bounds.Width; x++)
for (int column = 0; column < this.bounds.Width; column++)
{
DenseMatrixUtils.Convolve4(
in this.kernel,
yOffsetSpan,
xOffsetSpan,
this.sourcePixels,
ref spanRef,
y,
x,
this.bounds.Y,
this.maxY,
this.bounds.X,
this.maxX);
row,
column);
}
}

91
src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs

@ -0,0 +1,91 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Provides a map of the convolution kernel sampling offsets.
/// </summary>
internal sealed class KernelOffsetMap : IDisposable
{
private readonly MemoryAllocator allocator;
private bool isDisposed;
private IMemoryOwner<int> yOffsets;
private IMemoryOwner<int> xOffsets;
/// <summary>
/// Initializes a new instance of the <see cref="KernelOffsetMap"/> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
public KernelOffsetMap(MemoryAllocator allocator) => this.allocator = allocator;
public void BuildOffsetMap(in DenseMatrix<float> matrix, Rectangle bounds)
{
int matrixHeight = matrix.Rows;
int matrixWidth = matrix.Columns;
this.yOffsets = this.allocator.Allocate<int>(bounds.Height * matrixHeight);
this.xOffsets = this.allocator.Allocate<int>(bounds.Width * matrixWidth);
int minY = bounds.Y;
int maxY = bounds.Bottom - 1;
int minX = bounds.X;
int maxX = bounds.Right - 1;
int radiusY = matrixHeight >> 1;
int radiusX = matrixWidth >> 1;
// Calculate the potential sampling y-offsets.
Span<int> ySpan = this.yOffsets.GetSpan();
for (int row = 0; row < bounds.Height; row++)
{
for (int y = 0; y < matrixHeight; y++)
{
ySpan[(row * matrixHeight) + y] = row + y + minY - radiusY;
}
}
if (matrixHeight > 1)
{
Numerics.Clamp(ySpan, minY, maxY);
}
// Calculate the potential sampling x-offsets.
Span<int> xSpan = this.xOffsets.GetSpan();
for (int column = 0; column < bounds.Width; column++)
{
for (int x = 0; x < matrixWidth; x++)
{
xSpan[(column * matrixWidth) + x] = column + x + minX - radiusX;
}
}
if (matrixWidth > 1)
{
Numerics.Clamp(xSpan, minX, maxX);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<int> GetYOffsetSpan() => this.yOffsets.GetSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<int> GetXOffsetSpan() => this.xOffsets.GetSpan();
/// <inheritdoc/>
public void Dispose()
{
if (!this.isDisposed)
{
this.yOffsets.Dispose();
this.xOffsets.Dispose();
this.isDisposed = true;
}
}
}
}
Loading…
Cancel
Save