From 2eb09c3b8b406791c55be1cc1eb58259f1e18c81 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 4 Dec 2020 14:54:50 +0000 Subject: [PATCH 01/10] First working no-clamp version for 2 pass convolution --- .gitattributes | 3 + .../Common/Helpers/DenseMatrixUtils.cs | 58 +++++------- .../Convolution2PassProcessor{TPixel}.cs | 86 +++++++++++------- .../ConvolutionProcessor{TPixel}.cs | 53 ++++++----- .../Convolution/Kernels/KernelOffsetMap.cs | 91 +++++++++++++++++++ 5 files changed, 198 insertions(+), 93 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs diff --git a/.gitattributes b/.gitattributes index 3b7ad7e196..01a3825f8c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -105,8 +105,11 @@ *.pvr binary *.snk binary *.tga binary +*.tif binary +*.tiff binary *.ttc binary *.ttf binary +*.wbmp binary *.webp binary *.woff binary *.woff2 binary diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index f265bdd517..cf7eb1162a 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -6,6 +6,7 @@ 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 @@ public static void Convolve2DImpl( /// /// The pixel format. /// The dense matrix. + /// The span containing precalculated kernel y-offsets. + /// The span containing precalculated kernel x-offsets. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - /// The minimum working area row. - /// The maximum working area row. - /// The minimum working area column. - /// The maximum working area column. [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve3( in DenseMatrix matrix, + Span yOffsetSpan, + Span xOffsetSpan, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, - int column, - int minRow, - int maxRow, - int minColumn, - int maxColumn) + int column) where TPixel : unmanaged, IPixel { 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 @@ public static void Convolve3( /// /// The pixel format. /// The dense matrix. + /// The span containing precalculated kernel y-offsets. + /// The span containing precalculated kernel x-offsets. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - /// The minimum working area row. - /// The maximum working area row. - /// The minimum working area column. - /// The maximum working area column. [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve4( in DenseMatrix matrix, + Span yOffsetSpan, + Span xOffsetSpan, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, - int column, - int minRow, - int maxRow, - int minColumn, - int maxColumn) + int column) where TPixel : unmanaged, IPixel { 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 @@ public static void Convolve4( [MethodImpl(InliningOptions.ShortMethod)] private static void ConvolveImpl( in DenseMatrix matrix, + Span yOffsetSpan, + Span xOffsetSpan, Buffer2D sourcePixels, int row, int column, - int minRow, - int maxRow, - int minColumn, - int maxColumn, - ref Vector4 vector) + ref Vector4 targetVector) where TPixel : unmanaged, IPixel { 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 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; } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index b61690415a..bc17378c88 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -63,19 +63,45 @@ protected override void OnFrameApply(ImageFrame source) 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( - this.Configuration, - interest, - in horizontalOperation); - - // Vertical convolution - var verticalOperation = new RowOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( - 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( + 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( + this.Configuration, + interest, + in verticalOperation); + } } /// @@ -86,6 +112,7 @@ protected override void OnFrameApply(ImageFrame source) private readonly Rectangle bounds; private readonly Buffer2D targetPixels; private readonly Buffer2D sourcePixels; + private readonly KernelOffsetMap map; private readonly DenseMatrix kernel; private readonly Configuration configuration; private readonly bool preserveAlpha; @@ -95,6 +122,7 @@ public RowOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, + KernelOffsetMap map, DenseMatrix kernel, Configuration configuration, bool preserveAlpha) @@ -102,6 +130,7 @@ public RowOperation( 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 @@ public RowOperation( public void Invoke(int y, Span span) { ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); - - int maxY = this.bounds.Bottom - 1; - int maxX = this.bounds.Right - 1; - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + Span yOffsets = this.map.GetYOffsetSpan(); + Span 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); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 95fef15f62..b2c5de396f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -51,16 +51,22 @@ public ConvolutionProcessor( /// protected override void OnFrameApply(ImageFrame source) { - using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + MemoryAllocator allocator = this.Configuration.MemoryAllocator; + using Buffer2D targetPixels = allocator.Allocate2D(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( - 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( + this.Configuration, + interest, + in operation); + } Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -71,10 +77,9 @@ protected override void OnFrameApply(ImageFrame source) private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; - private readonly int maxY; - private readonly int maxX; private readonly Buffer2D targetPixels; private readonly Buffer2D sourcePixels; + private readonly KernelOffsetMap map; private readonly DenseMatrix kernel; private readonly Configuration configuration; private readonly bool preserveAlpha; @@ -84,15 +89,15 @@ public RowOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, + KernelOffsetMap map, DenseMatrix 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 @@ public RowOperation( public void Invoke(int y, Span span) { ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + Span yOffsetSpan = this.map.GetYOffsetSpan(); + Span 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); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs new file mode 100644 index 0000000000..c1adf357ca --- /dev/null +++ b/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 +{ + /// + /// Provides a map of the convolution kernel sampling offsets. + /// + internal sealed class KernelOffsetMap : IDisposable + { + private readonly MemoryAllocator allocator; + private bool isDisposed; + private IMemoryOwner yOffsets; + private IMemoryOwner xOffsets; + + /// + /// Initializes a new instance of the class. + /// + /// The memory allocator. + public KernelOffsetMap(MemoryAllocator allocator) => this.allocator = allocator; + + public void BuildOffsetMap(in DenseMatrix matrix, Rectangle bounds) + { + int matrixHeight = matrix.Rows; + int matrixWidth = matrix.Columns; + this.yOffsets = this.allocator.Allocate(bounds.Height * matrixHeight); + this.xOffsets = this.allocator.Allocate(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 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 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 GetYOffsetSpan() => this.yOffsets.GetSpan(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetXOffsetSpan() => this.xOffsets.GetSpan(); + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.yOffsets.Dispose(); + this.xOffsets.Dispose(); + + this.isDisposed = true; + } + } + } +} From a071d7e274d0fc26319519b602fa35a88eaa6c6c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 4 Dec 2020 15:05:15 +0000 Subject: [PATCH 02/10] Naming tweaks --- .../Convolution2PassProcessor{TPixel}.cs | 6 +++--- .../ConvolutionProcessor{TPixel}.cs | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index bc17378c88..63fbca98a9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -140,7 +140,7 @@ public RowOperation( [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + ref Vector4 targetRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); Span yOffsets = this.map.GetYOffsetSpan(); @@ -156,7 +156,7 @@ public void Invoke(int y, Span span) yOffsets, xOffsets, this.sourcePixels, - ref spanRef, + ref targetRef, row, column); } @@ -170,7 +170,7 @@ public void Invoke(int y, Span span) yOffsets, xOffsets, this.sourcePixels, - ref spanRef, + ref targetRef, row, column); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index b2c5de396f..ae2e8893f7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -107,11 +107,11 @@ public RowOperation( [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + ref Vector4 targetRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - Span yOffsetSpan = this.map.GetYOffsetSpan(); - Span xOffsetSpan = this.map.GetXOffsetSpan(); + Span yOffsets = this.map.GetYOffsetSpan(); + Span xOffsets = this.map.GetXOffsetSpan(); int row = y - this.bounds.Y; if (this.preserveAlpha) @@ -120,10 +120,10 @@ public void Invoke(int y, Span span) { DenseMatrixUtils.Convolve3( in this.kernel, - yOffsetSpan, - xOffsetSpan, + yOffsets, + xOffsets, this.sourcePixels, - ref spanRef, + ref targetRef, row, column); } @@ -134,10 +134,10 @@ public void Invoke(int y, Span span) { DenseMatrixUtils.Convolve4( in this.kernel, - yOffsetSpan, - xOffsetSpan, + yOffsets, + xOffsets, this.sourcePixels, - ref spanRef, + ref targetRef, row, column); } From 170d220c3f3f9c744edae5d29998f0a2ce15a21e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 4 Dec 2020 16:30:37 +0000 Subject: [PATCH 03/10] All tests pass --- .../Convolution2DProcessor{TPixel}.cs | 71 ++++--- .../Convolution2PassProcessor{TPixel}.cs | 22 +-- .../ConvolutionProcessor{TPixel}.cs | 18 +- .../Processors/Convolution/Convolver.cs} | 175 ++++++++---------- ...ernelOffsetMap.cs => KernelSamplingMap.cs} | 37 ++-- 5 files changed, 163 insertions(+), 160 deletions(-) rename src/ImageSharp/{Common/Helpers/DenseMatrixUtils.cs => Processing/Processors/Convolution/Convolver.cs} (57%) rename src/ImageSharp/Processing/Processors/Convolution/Kernels/{KernelOffsetMap.cs => KernelSamplingMap.cs} (66%) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 3a5f35cd14..8f1d373556 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -60,17 +60,32 @@ public Convolution2DProcessor( /// protected override void OnFrameApply(ImageFrame source) { - using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); + MemoryAllocator allocator = this.Configuration.MemoryAllocator; + using Buffer2D targetPixels = allocator.Allocate2D(source.Width, source.Height); source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha); + using (var map = new KernelSamplingMap(allocator)) + { + // Since the kernel sizes are identical we can use a single map. + map.BuildSamplingOffsetMap(this.KernelY, interest); + + var operation = new RowOperation( + interest, + targetPixels, + source.PixelBuffer, + map, + this.KernelY, + this.KernelX, + this.Configuration, + this.PreserveAlpha); - ParallelRowIterator.IterateRows( - this.Configuration, - interest, - in operation); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + } Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } @@ -81,10 +96,9 @@ protected override void OnFrameApply(ImageFrame source) private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; - private readonly int maxY; - private readonly int maxX; private readonly Buffer2D targetPixels; private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; private readonly DenseMatrix kernelY; private readonly DenseMatrix kernelX; private readonly Configuration configuration; @@ -95,16 +109,16 @@ public RowOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, + KernelSamplingMap map, DenseMatrix kernelY, DenseMatrix kernelX, 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.kernelY = kernelY; this.kernelX = kernelX; this.configuration = configuration; @@ -115,42 +129,41 @@ public RowOperation( [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + Span yOffsets = this.map.GetYOffsetSpan(); + Span 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.Convolve2D3( + Convolver.Convolve2D3( in this.kernelY, in this.kernelX, + yOffsets, + xOffsets, this.sourcePixels, - ref spanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); + ref targetRowRef, + row, + column); } } else { - for (int x = 0; x < this.bounds.Width; x++) + for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve2D4( + Convolver.Convolve2D4( in this.kernelY, in this.kernelX, + yOffsets, + xOffsets, this.sourcePixels, - ref spanRef, - y, - x, - this.bounds.Y, - this.maxY, - this.bounds.X, - this.maxX); + ref targetRowRef, + row, + column); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 63fbca98a9..2ea062e281 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -63,9 +63,9 @@ protected override void OnFrameApply(ImageFrame source) var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - using (var mapX = new KernelOffsetMap(this.Configuration.MemoryAllocator)) + using (var mapX = new KernelSamplingMap(this.Configuration.MemoryAllocator)) { - mapX.BuildOffsetMap(this.KernelX, interest); + mapX.BuildSamplingOffsetMap(this.KernelX, interest); // Horizontal convolution var horizontalOperation = new RowOperation( @@ -83,9 +83,9 @@ protected override void OnFrameApply(ImageFrame source) in horizontalOperation); } - using (var mapY = new KernelOffsetMap(this.Configuration.MemoryAllocator)) + using (var mapY = new KernelSamplingMap(this.Configuration.MemoryAllocator)) { - mapY.BuildOffsetMap(this.KernelY, interest); + mapY.BuildSamplingOffsetMap(this.KernelY, interest); // Vertical convolution var verticalOperation = new RowOperation( @@ -112,7 +112,7 @@ protected override void OnFrameApply(ImageFrame source) private readonly Rectangle bounds; private readonly Buffer2D targetPixels; private readonly Buffer2D sourcePixels; - private readonly KernelOffsetMap map; + private readonly KernelSamplingMap map; private readonly DenseMatrix kernel; private readonly Configuration configuration; private readonly bool preserveAlpha; @@ -122,7 +122,7 @@ public RowOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, - KernelOffsetMap map, + KernelSamplingMap map, DenseMatrix kernel, Configuration configuration, bool preserveAlpha) @@ -140,7 +140,7 @@ public RowOperation( [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - ref Vector4 targetRef = ref MemoryMarshal.GetReference(span); + ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); Span yOffsets = this.map.GetYOffsetSpan(); @@ -151,12 +151,12 @@ public void Invoke(int y, Span span) { for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve3( + Convolver.Convolve3( in this.kernel, yOffsets, xOffsets, this.sourcePixels, - ref targetRef, + ref targetRowRef, row, column); } @@ -165,12 +165,12 @@ public void Invoke(int y, Span span) { for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve4( + Convolver.Convolve4( in this.kernel, yOffsets, xOffsets, this.sourcePixels, - ref targetRef, + ref targetRowRef, row, column); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index ae2e8893f7..999fba22be 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -57,9 +57,9 @@ protected override void OnFrameApply(ImageFrame source) source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - using (var map = new KernelOffsetMap(allocator)) + using (var map = new KernelSamplingMap(allocator)) { - map.BuildOffsetMap(this.KernelXY, interest); + map.BuildSamplingOffsetMap(this.KernelXY, interest); var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows( @@ -79,7 +79,7 @@ protected override void OnFrameApply(ImageFrame source) private readonly Rectangle bounds; private readonly Buffer2D targetPixels; private readonly Buffer2D sourcePixels; - private readonly KernelOffsetMap map; + private readonly KernelSamplingMap map; private readonly DenseMatrix kernel; private readonly Configuration configuration; private readonly bool preserveAlpha; @@ -89,7 +89,7 @@ public RowOperation( Rectangle bounds, Buffer2D targetPixels, Buffer2D sourcePixels, - KernelOffsetMap map, + KernelSamplingMap map, DenseMatrix kernel, Configuration configuration, bool preserveAlpha) @@ -107,7 +107,7 @@ public RowOperation( [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - ref Vector4 targetRef = ref MemoryMarshal.GetReference(span); + ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); Span yOffsets = this.map.GetYOffsetSpan(); @@ -118,12 +118,12 @@ public void Invoke(int y, Span span) { for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve3( + Convolver.Convolve3( in this.kernel, yOffsets, xOffsets, this.sourcePixels, - ref targetRef, + ref targetRowRef, row, column); } @@ -132,12 +132,12 @@ public void Invoke(int y, Span span) { for (int column = 0; column < this.bounds.Width; column++) { - DenseMatrixUtils.Convolve4( + Convolver.Convolve4( in this.kernel, yOffsets, xOffsets, this.sourcePixels, - ref targetRef, + ref targetRowRef, row, column); } diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs similarity index 57% rename from src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs rename to src/ImageSharp/Processing/Processors/Convolution/Convolver.cs index cf7eb1162a..c9e9d74148 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs @@ -6,56 +6,50 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp { /// - /// Extension methods for . - /// TODO: One day rewrite all this to use SIMD intrinsics. There's a lot of scope for improvement. + /// Provides methods to perform convolution operations. /// - internal static class DenseMatrixUtils + internal static class Convolver { /// /// Computes the sum of vectors in the span referenced by weighted by the two kernel weight values. /// Using this method the convolution filter is not applied to alpha in addition to the color channels. /// /// The pixel format. - /// The vertical dense matrix. - /// The horizontal dense matrix. + /// The vertical convolution kernel. + /// The horizontal convolution kernel. + /// The span containing precalculated kernel y-sampling offsets. + /// The span containing precalculated kernel x-sampling offsets. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - /// The minimum working area row. - /// The maximum working area row. - /// The minimum working area column. - /// The maximum working area column. [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve2D3( - in DenseMatrix matrixY, - in DenseMatrix matrixX, + in DenseMatrix kernelY, + in DenseMatrix kernelX, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, - int column, - int minRow, - int maxRow, - int minColumn, - int maxColumn) + int column) where TPixel : unmanaged, IPixel { + Vector4 vector = default; + Convolve2DImpl( - in matrixY, - in matrixX, + in kernelY, + in kernelX, + rowSampleOffsets, + columnSampleOffsets, sourcePixels, row, column, - minRow, - maxRow, - minColumn, - maxColumn, - out Vector4 vector); + ref vector); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; @@ -69,41 +63,37 @@ public static void Convolve2D3( /// Using this method the convolution filter is applied to alpha in addition to the color channels. /// /// The pixel format. - /// The vertical dense matrix. - /// The horizontal dense matrix. + /// The vertical convolution kernel. + /// The horizontal convolution kernel. + /// The span containing precalculated kernel y-sampling offsets. + /// The span containing precalculated kernel x-sampling offsets. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - /// The minimum working area row. - /// The maximum working area row. - /// The minimum working area column. - /// The maximum working area column. [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve2D4( - in DenseMatrix matrixY, - in DenseMatrix matrixX, + in DenseMatrix kernelY, + in DenseMatrix kernelX, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, - int column, - int minRow, - int maxRow, - int minColumn, - int maxColumn) + int column) where TPixel : unmanaged, IPixel { + Vector4 vector = default; + Convolve2DImpl( - in matrixY, - in matrixX, + in kernelY, + in kernelX, + rowSampleOffsets, + columnSampleOffsets, sourcePixels, row, column, - minRow, - maxRow, - minColumn, - maxColumn, - out Vector4 vector); + ref vector); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); Numerics.UnPremultiply(ref vector); @@ -112,43 +102,38 @@ public static void Convolve2D4( [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve2DImpl( - in DenseMatrix matrixY, - in DenseMatrix matrixX, + in DenseMatrix kernelY, + in DenseMatrix kernelX, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, int row, int column, - int minRow, - int maxRow, - int minColumn, - int maxColumn, - out Vector4 vector) + ref Vector4 targetVector) where TPixel : unmanaged, IPixel { Vector4 vectorY = default; Vector4 vectorX = default; - int matrixHeight = matrixY.Rows; - int matrixWidth = matrixY.Columns; - int radiusY = matrixHeight >> 1; - int radiusX = matrixWidth >> 1; - int sourceOffsetColumnBase = column + minColumn; + int kernelHeight = kernelY.Rows; + int kernelWidth = kernelY.Columns; - for (int y = 0; y < matrixHeight; y++) + for (int y = 0; y < kernelHeight; y++) { - int offsetY = Numerics.Clamp(row + y - radiusY, minRow, maxRow); + int offsetY = rowSampleOffsets[(row * kernelHeight) + y]; Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); - for (int x = 0; x < matrixWidth; x++) + for (int x = 0; x < kernelWidth; x++) { - int offsetX = Numerics.Clamp(sourceOffsetColumnBase + x - radiusX, minColumn, maxColumn); - var currentColor = sourceRowSpan[offsetX].ToVector4(); - Numerics.Premultiply(ref currentColor); + int offsetX = columnSampleOffsets[(column * kernelWidth) + x]; + var sample = sourceRowSpan[offsetX].ToVector4(); + Numerics.Premultiply(ref sample); - vectorX += matrixX[y, x] * currentColor; - vectorY += matrixY[y, x] * currentColor; + vectorX += kernelX[y, x] * sample; + vectorY += kernelY[y, x] * sample; } } - vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + targetVector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); } /// @@ -156,18 +141,18 @@ public static void Convolve2DImpl( /// Using this method the convolution filter is not applied to alpha in addition to the color channels. /// /// The pixel format. - /// The dense matrix. - /// The span containing precalculated kernel y-offsets. - /// The span containing precalculated kernel x-offsets. + /// The convolution kernel. + /// The span containing precalculated kernel y-sampling offsets. + /// The span containing precalculated kernel x-sampling offsets. /// The source frame. /// The target row base reference. /// The current row. /// The current column. [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve3( - in DenseMatrix matrix, - Span yOffsetSpan, - Span xOffsetSpan, + in DenseMatrix kernel, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -177,9 +162,9 @@ public static void Convolve3( Vector4 vector = default; ConvolveImpl( - in matrix, - yOffsetSpan, - xOffsetSpan, + in kernel, + rowSampleOffsets, + columnSampleOffsets, sourcePixels, row, column, @@ -197,18 +182,18 @@ public static void Convolve3( /// Using this method the convolution filter is applied to alpha in addition to the color channels. /// /// The pixel format. - /// The dense matrix. - /// The span containing precalculated kernel y-offsets. - /// The span containing precalculated kernel x-offsets. + /// The convolution kernel. + /// The span containing precalculated kernel y-offsets. + /// The span containing precalculated kernel x-offsets. /// The source frame. /// The target row base reference. /// The current row. /// The current column. [MethodImpl(InliningOptions.ShortMethod)] public static void Convolve4( - in DenseMatrix matrix, - Span yOffsetSpan, - Span xOffsetSpan, + in DenseMatrix kernel, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -218,9 +203,9 @@ public static void Convolve4( Vector4 vector = default; ConvolveImpl( - in matrix, - yOffsetSpan, - xOffsetSpan, + in kernel, + rowSampleOffsets, + columnSampleOffsets, sourcePixels, row, column, @@ -233,29 +218,29 @@ public static void Convolve4( [MethodImpl(InliningOptions.ShortMethod)] private static void ConvolveImpl( - in DenseMatrix matrix, - Span yOffsetSpan, - Span xOffsetSpan, + in DenseMatrix kernel, + Span rowSampleOffsets, + Span columnSampleOffsets, Buffer2D sourcePixels, int row, int column, ref Vector4 targetVector) where TPixel : unmanaged, IPixel { - int matrixHeight = matrix.Rows; - int matrixWidth = matrix.Columns; + int kernelHeight = kernel.Rows; + int kernelWidth = kernel.Columns; - for (int y = 0; y < matrixHeight; y++) + for (int y = 0; y < kernelHeight; y++) { - int offsetY = yOffsetSpan[(row * matrixHeight) + y]; + int offsetY = rowSampleOffsets[(row * kernelHeight) + y]; Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); - for (int x = 0; x < matrixWidth; x++) + for (int x = 0; x < kernelWidth; x++) { - int offsetX = xOffsetSpan[(column * matrixWidth) + x]; - var currentColor = sourceRowSpan[offsetX].ToVector4(); - Numerics.Premultiply(ref currentColor); - targetVector += matrix[y, x] * currentColor; + int offsetX = columnSampleOffsets[(column * kernelWidth) + x]; + var sample = sourceRowSpan[offsetX].ToVector4(); + Numerics.Premultiply(ref sample); + targetVector += kernel[y, x] * sample; } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs similarity index 66% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs rename to src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs index c1adf357ca..493c0d0fd2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// Provides a map of the convolution kernel sampling offsets. /// - internal sealed class KernelOffsetMap : IDisposable + internal sealed class KernelSamplingMap : IDisposable { private readonly MemoryAllocator allocator; private bool isDisposed; @@ -19,37 +19,42 @@ internal sealed class KernelOffsetMap : IDisposable private IMemoryOwner xOffsets; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The memory allocator. - public KernelOffsetMap(MemoryAllocator allocator) => this.allocator = allocator; + public KernelSamplingMap(MemoryAllocator allocator) => this.allocator = allocator; - public void BuildOffsetMap(in DenseMatrix matrix, Rectangle bounds) + /// + /// Builds a map of the sampling offsets for the kernel clamped by the given bounds. + /// + /// The convolution kernel. + /// The source bounds. + public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds) { - int matrixHeight = matrix.Rows; - int matrixWidth = matrix.Columns; - this.yOffsets = this.allocator.Allocate(bounds.Height * matrixHeight); - this.xOffsets = this.allocator.Allocate(bounds.Width * matrixWidth); + int kernelHeight = kernel.Rows; + int kernelWidth = kernel.Columns; + this.yOffsets = this.allocator.Allocate(bounds.Height * kernelHeight); + this.xOffsets = this.allocator.Allocate(bounds.Width * kernelWidth); 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; + int radiusY = kernelHeight >> 1; + int radiusX = kernelWidth >> 1; // Calculate the potential sampling y-offsets. Span ySpan = this.yOffsets.GetSpan(); for (int row = 0; row < bounds.Height; row++) { - for (int y = 0; y < matrixHeight; y++) + for (int y = 0; y < kernelHeight; y++) { - ySpan[(row * matrixHeight) + y] = row + y + minY - radiusY; + ySpan[(row * kernelHeight) + y] = row + y + minY - radiusY; } } - if (matrixHeight > 1) + if (kernelHeight > 1) { Numerics.Clamp(ySpan, minY, maxY); } @@ -58,13 +63,13 @@ public void BuildOffsetMap(in DenseMatrix matrix, Rectangle bounds) Span xSpan = this.xOffsets.GetSpan(); for (int column = 0; column < bounds.Width; column++) { - for (int x = 0; x < matrixWidth; x++) + for (int x = 0; x < kernelWidth; x++) { - xSpan[(column * matrixWidth) + x] = column + x + minX - radiusX; + xSpan[(column * kernelWidth) + x] = column + x + minX - radiusX; } } - if (matrixWidth > 1) + if (kernelWidth > 1) { Numerics.Clamp(xSpan, minX, maxX); } From c6c867cc7e05ec07ae075a66ccfe3006c2249a77 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 5 Dec 2020 01:30:47 +0000 Subject: [PATCH 04/10] Refactor 2D and cleanup --- src/ImageSharp/Primitives/DenseMatrix{T}.cs | 16 +-- .../Convolution2DProcessor{TPixel}.cs | 18 +-- .../Convolution/Convolution2DState.cs | 54 +++++++++ .../Convolution2PassProcessor{TPixel}.cs | 16 +-- .../ConvolutionProcessor{TPixel}.cs | 14 +-- .../Convolution/ConvolutionState.cs | 45 ++++++++ .../Processors/Convolution/Convolver.cs | 103 ++++++------------ .../{Kernels => }/KernelSamplingMap.cs | 4 +- .../Processors/Convolution/ReadOnlyKernel.cs | 63 +++++++++++ 9 files changed, 225 insertions(+), 108 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs create mode 100644 src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs rename src/ImageSharp/Processing/Processors/Convolution/{Kernels => }/KernelSamplingMap.cs (95%) create mode 100644 src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index e312703368..60dadb617b 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -109,7 +109,7 @@ public DenseMatrix(T[,] data) /// The at the specified position. public ref T this[int row, int column] { - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { this.CheckCoordinates(row, column); @@ -124,7 +124,7 @@ public DenseMatrix(T[,] data) /// /// The representation on the source data. /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator DenseMatrix(T[,] data) => new DenseMatrix(data); /// @@ -134,7 +134,7 @@ public DenseMatrix(T[,] data) /// /// The representation on the source data. /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] #pragma warning disable SA1008 // Opening parenthesis should be spaced correctly public static implicit operator T[,](in DenseMatrix data) #pragma warning restore SA1008 // Opening parenthesis should be spaced correctly @@ -175,7 +175,7 @@ public DenseMatrix(T[,] data) /// Transposes the rows and columns of the dense matrix. /// /// The . - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public DenseMatrix Transpose() { var result = new DenseMatrix(this.Rows, this.Columns); @@ -196,13 +196,13 @@ public DenseMatrix Transpose() /// Fills the matrix with the given value /// /// The value to fill each item with - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Fill(T value) => this.Span.Fill(value); /// /// Clears the matrix setting each value to the default value for the element type /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() => this.Span.Clear(); /// @@ -232,14 +232,14 @@ public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(DenseMatrix other) => this.Columns == other.Columns && this.Rows == other.Rows && this.Span.SequenceEqual(other.Span); /// - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { HashCode code = default; diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 8f1d373556..249c73e8d6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -43,12 +43,12 @@ public Convolution2DProcessor( } /// - /// Gets the horizontal gradient operator. + /// Gets the horizontal convolution kernel. /// public DenseMatrix KernelX { get; } /// - /// Gets the vertical gradient operator. + /// Gets the vertical convolution kernel. /// public DenseMatrix KernelY { get; } @@ -132,8 +132,8 @@ public void Invoke(int y, Span span) ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - Span yOffsets = this.map.GetYOffsetSpan(); - Span xOffsets = this.map.GetXOffsetSpan(); + + var state = new Convolution2DState(this.kernelY, this.kernelX, this.map); int row = y - this.bounds.Y; if (this.preserveAlpha) @@ -141,10 +141,7 @@ public void Invoke(int y, Span span) for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve2D3( - in this.kernelY, - in this.kernelX, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, @@ -156,10 +153,7 @@ public void Invoke(int y, Span span) for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve2D4( - in this.kernelY, - in this.kernelX, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs new file mode 100644 index 0000000000..e36d458a4a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// A stack only struct used for reducing reference indirection during 2D convolution operations. + /// + internal readonly ref struct Convolution2DState + { + private readonly Span rowOffsetMap; + private readonly Span columnOffsetMap; + private readonly int kernelHeight; + private readonly int kernelWidth; + + public Convolution2DState( + in DenseMatrix kernelY, + in DenseMatrix kernelX, + KernelSamplingMap map) + { + // We check the kernels are the same size upstream. + this.KernelY = new ReadOnlyKernel(kernelY); + this.KernelX = new ReadOnlyKernel(kernelX); + this.kernelHeight = kernelY.Rows; + this.kernelWidth = kernelY.Columns; + this.rowOffsetMap = map.GetRowOffsetSpan(); + this.columnOffsetMap = map.GetColumnOffsetSpan(); + } + + public ReadOnlyKernel KernelY + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + public ReadOnlyKernel KernelX + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetRowSampleOffset(int row, int kernelRow) + => Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), (row * this.kernelHeight) + kernelRow); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetColumnSampleOffset(int column, int kernelColumn) + => Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), (column * this.kernelWidth) + kernelColumn); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 2ea062e281..95fd3b83cc 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -42,12 +42,12 @@ public Convolution2PassProcessor( } /// - /// Gets the horizontal gradient operator. + /// Gets the horizontal convolution kernel. /// public DenseMatrix KernelX { get; } /// - /// Gets the vertical gradient operator. + /// Gets the vertical convolution kernel. /// public DenseMatrix KernelY { get; } @@ -143,8 +143,8 @@ public void Invoke(int y, Span span) ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - Span yOffsets = this.map.GetYOffsetSpan(); - Span xOffsets = this.map.GetXOffsetSpan(); + + var state = new ConvolutionState(this.kernel, this.map); int row = y - this.bounds.Y; if (this.preserveAlpha) @@ -152,9 +152,7 @@ public void Invoke(int y, Span span) for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve3( - in this.kernel, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, @@ -166,9 +164,7 @@ public void Invoke(int y, Span span) for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve4( - in this.kernel, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 999fba22be..191460f40b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -39,7 +39,7 @@ public ConvolutionProcessor( } /// - /// Gets the 2d gradient operator. + /// Gets the 2d convolution kernel. /// public DenseMatrix KernelXY { get; } @@ -110,8 +110,8 @@ public void Invoke(int y, Span span) ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - Span yOffsets = this.map.GetYOffsetSpan(); - Span xOffsets = this.map.GetXOffsetSpan(); + + var state = new ConvolutionState(this.kernel, this.map); int row = y - this.bounds.Y; if (this.preserveAlpha) @@ -119,9 +119,7 @@ public void Invoke(int y, Span span) for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve3( - in this.kernel, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, @@ -133,9 +131,7 @@ public void Invoke(int y, Span span) for (int column = 0; column < this.bounds.Width; column++) { Convolver.Convolve4( - in this.kernel, - yOffsets, - xOffsets, + in state, this.sourcePixels, ref targetRowRef, row, diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs new file mode 100644 index 0000000000..97a3af342e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// A stack only struct used for reducing reference indirection during convolution operations. + /// + internal readonly ref struct ConvolutionState + { + private readonly Span rowOffsetMap; + private readonly Span columnOffsetMap; + private readonly int kernelHeight; + private readonly int kernelWidth; + + public ConvolutionState( + in DenseMatrix kernel, + KernelSamplingMap map) + { + this.Kernel = new ReadOnlyKernel(kernel); + this.kernelHeight = kernel.Rows; + this.kernelWidth = kernel.Columns; + this.rowOffsetMap = map.GetRowOffsetSpan(); + this.columnOffsetMap = map.GetColumnOffsetSpan(); + } + + public ReadOnlyKernel Kernel + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetRowSampleOffset(int row, int kernelRow) + => Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), (row * this.kernelHeight) + kernelRow); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetColumnSampleOffset(int column, int kernelColumn) + => Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), (column * this.kernelWidth) + kernelColumn); + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs index c9e9d74148..5ddc8e85c6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs @@ -1,11 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp { @@ -19,20 +20,14 @@ internal static class Convolver /// Using this method the convolution filter is not applied to alpha in addition to the color channels. /// /// The pixel format. - /// The vertical convolution kernel. - /// The horizontal convolution kernel. - /// The span containing precalculated kernel y-sampling offsets. - /// The span containing precalculated kernel x-sampling offsets. + /// The 2D convolution kernels state. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Convolve2D3( - in DenseMatrix kernelY, - in DenseMatrix kernelX, - Span rowSampleOffsets, - Span columnSampleOffsets, + in Convolution2DState state, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -42,10 +37,7 @@ public static void Convolve2D3( Vector4 vector = default; Convolve2DImpl( - in kernelY, - in kernelX, - rowSampleOffsets, - columnSampleOffsets, + in state, sourcePixels, row, column, @@ -63,20 +55,14 @@ public static void Convolve2D3( /// Using this method the convolution filter is applied to alpha in addition to the color channels. /// /// The pixel format. - /// The vertical convolution kernel. - /// The horizontal convolution kernel. - /// The span containing precalculated kernel y-sampling offsets. - /// The span containing precalculated kernel x-sampling offsets. + /// The 2D convolution kernels state. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Convolve2D4( - in DenseMatrix kernelY, - in DenseMatrix kernelX, - Span rowSampleOffsets, - Span columnSampleOffsets, + in Convolution2DState state, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -86,10 +72,7 @@ public static void Convolve2D4( Vector4 vector = default; Convolve2DImpl( - in kernelY, - in kernelX, - rowSampleOffsets, - columnSampleOffsets, + in state, sourcePixels, row, column, @@ -100,34 +83,33 @@ public static void Convolve2D4( target = vector; } - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Convolve2DImpl( - in DenseMatrix kernelY, - in DenseMatrix kernelX, - Span rowSampleOffsets, - Span columnSampleOffsets, + in Convolution2DState state, Buffer2D sourcePixels, int row, int column, ref Vector4 targetVector) where TPixel : unmanaged, IPixel { - Vector4 vectorY = default; - Vector4 vectorX = default; + ReadOnlyKernel kernelY = state.KernelY; + ReadOnlyKernel kernelX = state.KernelX; int kernelHeight = kernelY.Rows; int kernelWidth = kernelY.Columns; + Vector4 vectorY = default; + Vector4 vectorX = default; + for (int y = 0; y < kernelHeight; y++) { - int offsetY = rowSampleOffsets[(row * kernelHeight) + y]; - Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + int offsetY = state.GetRowSampleOffset(row, y); + ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY)); for (int x = 0; x < kernelWidth; x++) { - int offsetX = columnSampleOffsets[(column * kernelWidth) + x]; - var sample = sourceRowSpan[offsetX].ToVector4(); + int offsetX = state.GetColumnSampleOffset(column, x); + var sample = Unsafe.Add(ref sourceRowBase, offsetX).ToVector4(); Numerics.Premultiply(ref sample); - vectorX += kernelX[y, x] * sample; vectorY += kernelY[y, x] * sample; } @@ -141,18 +123,14 @@ public static void Convolve2DImpl( /// Using this method the convolution filter is not applied to alpha in addition to the color channels. /// /// The pixel format. - /// The convolution kernel. - /// The span containing precalculated kernel y-sampling offsets. - /// The span containing precalculated kernel x-sampling offsets. + /// The convolution kernel state. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Convolve3( - in DenseMatrix kernel, - Span rowSampleOffsets, - Span columnSampleOffsets, + in ConvolutionState state, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -162,9 +140,7 @@ public static void Convolve3( Vector4 vector = default; ConvolveImpl( - in kernel, - rowSampleOffsets, - columnSampleOffsets, + state, sourcePixels, row, column, @@ -182,18 +158,14 @@ public static void Convolve3( /// Using this method the convolution filter is applied to alpha in addition to the color channels. /// /// The pixel format. - /// The convolution kernel. - /// The span containing precalculated kernel y-offsets. - /// The span containing precalculated kernel x-offsets. + /// The convolution kernel state. /// The source frame. /// The target row base reference. /// The current row. /// The current column. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Convolve4( - in DenseMatrix kernel, - Span rowSampleOffsets, - Span columnSampleOffsets, + in ConvolutionState state, Buffer2D sourcePixels, ref Vector4 targetRowRef, int row, @@ -203,9 +175,7 @@ public static void Convolve4( Vector4 vector = default; ConvolveImpl( - in kernel, - rowSampleOffsets, - columnSampleOffsets, + state, sourcePixels, row, column, @@ -216,29 +186,28 @@ public static void Convolve4( target = vector; } - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ConvolveImpl( - in DenseMatrix kernel, - Span rowSampleOffsets, - Span columnSampleOffsets, + in ConvolutionState state, Buffer2D sourcePixels, int row, int column, ref Vector4 targetVector) where TPixel : unmanaged, IPixel { + ReadOnlyKernel kernel = state.Kernel; int kernelHeight = kernel.Rows; int kernelWidth = kernel.Columns; for (int y = 0; y < kernelHeight; y++) { - int offsetY = rowSampleOffsets[(row * kernelHeight) + y]; - Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + int offsetY = state.GetRowSampleOffset(row, y); + ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY)); for (int x = 0; x < kernelWidth; x++) { - int offsetX = columnSampleOffsets[(column * kernelWidth) + x]; - var sample = sourceRowSpan[offsetX].ToVector4(); + int offsetX = state.GetColumnSampleOffset(column, x); + var sample = Unsafe.Add(ref sourceRowBase, offsetX).ToVector4(); Numerics.Premultiply(ref sample); targetVector += kernel[y, x] * sample; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs similarity index 95% rename from src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs rename to src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs index 493c0d0fd2..73a4fa4004 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelSamplingMap.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs @@ -76,10 +76,10 @@ public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetYOffsetSpan() => this.yOffsets.GetSpan(); + public Span GetRowOffsetSpan() => this.yOffsets.GetSpan(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetXOffsetSpan() => this.xOffsets.GetSpan(); + public Span GetColumnOffsetSpan() => this.xOffsets.GetSpan(); /// public void Dispose() diff --git a/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs b/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs new file mode 100644 index 0000000000..37e0060054 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ReadOnlyKernel.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// A stack only, readonly, kernel matrix that can be indexed without + /// bounds checks when compiled in release mode. + /// + internal readonly ref struct ReadOnlyKernel + { + private readonly ReadOnlySpan values; + + public ReadOnlyKernel(DenseMatrix 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 float this[int row, int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + this.CheckCoordinates(row, column); + ref float vBase = ref MemoryMarshal.GetReference(this.values); + return Unsafe.Add(ref vBase, (row * this.Columns) + column); + } + } + + [Conditional("DEBUG")] + private void CheckCoordinates(int row, int column) + { + if (row < 0 || row >= this.Rows) + { + throw new ArgumentOutOfRangeException(nameof(row), row, $"{row} is outwith the matrix bounds."); + } + + if (column < 0 || column >= this.Columns) + { + throw new ArgumentOutOfRangeException(nameof(column), column, $"{column} is outwith the matrix bounds."); + } + } + } +} From 81f21e527eca5db24c862004c87877b878b82636 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 5 Dec 2020 13:55:49 +0000 Subject: [PATCH 05/10] Update KernelSamplingMap.cs --- .../Processors/Convolution/KernelSamplingMap.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs index 73a4fa4004..144d356c6e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -44,13 +45,16 @@ public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds) int radiusY = kernelHeight >> 1; int radiusX = kernelWidth >> 1; - // Calculate the potential sampling y-offsets. + // Calculate the y and x sampling offsets clamped to the given rectangle. + // While this isn't a hotpath we still dip into unsafe to avoid the span bounds + // checks as the can potentially be looping over large arrays. Span ySpan = this.yOffsets.GetSpan(); + ref int ySpanBase = ref MemoryMarshal.GetReference(ySpan); for (int row = 0; row < bounds.Height; row++) { for (int y = 0; y < kernelHeight; y++) { - ySpan[(row * kernelHeight) + y] = row + y + minY - radiusY; + Unsafe.Add(ref ySpanBase, (row * kernelHeight) + y) = row + y + minY - radiusY; } } @@ -59,13 +63,13 @@ public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds) Numerics.Clamp(ySpan, minY, maxY); } - // Calculate the potential sampling x-offsets. Span xSpan = this.xOffsets.GetSpan(); + ref int xSpanBase = ref MemoryMarshal.GetReference(xSpan); for (int column = 0; column < bounds.Width; column++) { for (int x = 0; x < kernelWidth; x++) { - xSpan[(column * kernelWidth) + x] = column + x + minX - radiusX; + Unsafe.Add(ref xSpanBase, (column * kernelWidth) + x) = column + x + minX - radiusX; } } From 6e768875f24ad87d7e069d53d0295c9265079064 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 5 Dec 2020 23:51:12 +0000 Subject: [PATCH 06/10] Explicit in --- .../Processors/Convolution/Convolution2PassProcessor{TPixel}.cs | 2 +- .../Processors/Convolution/ConvolutionProcessor{TPixel}.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 95fd3b83cc..e05892fa54 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -144,7 +144,7 @@ public void Invoke(int y, Span span) Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - var state = new ConvolutionState(this.kernel, this.map); + var state = new ConvolutionState(in this.kernel, this.map); int row = y - this.bounds.Y; if (this.preserveAlpha) diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 191460f40b..d6be1dc563 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -111,7 +111,7 @@ public void Invoke(int y, Span span) Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - var state = new ConvolutionState(this.kernel, this.map); + var state = new ConvolutionState(in this.kernel, this.map); int row = y - this.bounds.Y; if (this.preserveAlpha) From 811600a6587aecd228fd7cc00dfbab2807aec982 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 6 Dec 2020 00:27:14 +0000 Subject: [PATCH 07/10] Use faster GetSpan() --- .../Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs index a7a51f77dd..0c35c88286 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs @@ -53,8 +53,13 @@ public override Span GetSpan() { ThrowObjectDisposedException(); } - +#if SUPPORTS_CREATESPAN + ref byte r0 = ref MemoryMarshal.GetReference(this.Data); + return MemoryMarshal.CreateSpan(ref Unsafe.As(ref r0), this.length); +#else return MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length); +#endif + } /// From 656ca73d9885dbb59f1a6b489799bab3441422cd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 6 Dec 2020 13:54:23 +0000 Subject: [PATCH 08/10] Avoid per-index multiply. --- .../Processors/Convolution/Convolution2DState.cs | 12 ++++++------ .../Processors/Convolution/ConvolutionState.cs | 10 +++++----- .../Processing/Processors/Convolution/Convolver.cs | 12 ++++++++---- .../Processors/Convolution/KernelSamplingMap.cs | 6 ++++-- tests/ImageSharp.Benchmarks/Config.cs | 8 ++++++++ tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs | 2 +- 6 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs index e36d458a4a..9d17ebab05 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs @@ -31,24 +31,24 @@ public Convolution2DState( this.columnOffsetMap = map.GetColumnOffsetSpan(); } - public ReadOnlyKernel KernelY + public readonly ReadOnlyKernel KernelY { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } - public ReadOnlyKernel KernelX + public readonly ReadOnlyKernel KernelX { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetRowSampleOffset(int row, int kernelRow) - => Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), (row * this.kernelHeight) + kernelRow); + public readonly ref int GetSampleOffsetRow(int row) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetColumnSampleOffset(int column, int kernelColumn) - => Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), (column * this.kernelWidth) + kernelColumn); + public readonly ref int GetSampleOffsetColumn(int column) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs index 97a3af342e..851eeec247 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs @@ -28,18 +28,18 @@ public ConvolutionState( this.columnOffsetMap = map.GetColumnOffsetSpan(); } - public ReadOnlyKernel Kernel + public readonly ReadOnlyKernel Kernel { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetRowSampleOffset(int row, int kernelRow) - => Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), (row * this.kernelHeight) + kernelRow); + public readonly ref int GetSampleOffsetRow(int row) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.rowOffsetMap), row * this.kernelHeight); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetColumnSampleOffset(int column, int kernelColumn) - => Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), (column * this.kernelWidth) + kernelColumn); + public readonly ref int GetSampleOffsetColumn(int column) + => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs index 5ddc8e85c6..c23b71b330 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs @@ -100,14 +100,16 @@ public static void Convolve2DImpl( Vector4 vectorY = default; Vector4 vectorX = default; + ref int sampleOffsetRowBase = ref state.GetSampleOffsetRow(row); for (int y = 0; y < kernelHeight; y++) { - int offsetY = state.GetRowSampleOffset(row, y); + int offsetY = Unsafe.Add(ref sampleOffsetRowBase, y); ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY)); + ref int sampleOffsetColumnBase = ref state.GetSampleOffsetColumn(column); for (int x = 0; x < kernelWidth; x++) { - int offsetX = state.GetColumnSampleOffset(column, x); + int offsetX = Unsafe.Add(ref sampleOffsetColumnBase, x); var sample = Unsafe.Add(ref sourceRowBase, offsetX).ToVector4(); Numerics.Premultiply(ref sample); vectorX += kernelX[y, x] * sample; @@ -199,14 +201,16 @@ private static void ConvolveImpl( int kernelHeight = kernel.Rows; int kernelWidth = kernel.Columns; + ref int sampleOffsetRowBase = ref state.GetSampleOffsetRow(row); for (int y = 0; y < kernelHeight; y++) { - int offsetY = state.GetRowSampleOffset(row, y); + int offsetY = Unsafe.Add(ref sampleOffsetRowBase, y); ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY)); + ref int sampleOffsetColumnBase = ref state.GetSampleOffsetColumn(column); for (int x = 0; x < kernelWidth; x++) { - int offsetX = state.GetColumnSampleOffset(column, x); + int offsetX = Unsafe.Add(ref sampleOffsetColumnBase, x); var sample = Unsafe.Add(ref sourceRowBase, offsetX).ToVector4(); Numerics.Premultiply(ref sample); targetVector += kernel[y, x] * sample; diff --git a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs index 144d356c6e..e4b7dbea09 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs @@ -52,9 +52,10 @@ public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds) ref int ySpanBase = ref MemoryMarshal.GetReference(ySpan); for (int row = 0; row < bounds.Height; row++) { + int rowBase = row * kernelHeight; for (int y = 0; y < kernelHeight; y++) { - Unsafe.Add(ref ySpanBase, (row * kernelHeight) + y) = row + y + minY - radiusY; + Unsafe.Add(ref ySpanBase, rowBase + y) = row + y + minY - radiusY; } } @@ -67,9 +68,10 @@ public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds) ref int xSpanBase = ref MemoryMarshal.GetReference(xSpan); for (int column = 0; column < bounds.Width; column++) { + int columnBase = column * kernelWidth; for (int x = 0; x < kernelWidth; x++) { - Unsafe.Add(ref xSpanBase, (column * kernelWidth) + x) = column + x + minX - radiusX; + Unsafe.Add(ref xSpanBase, columnBase + x) = column + x + minX - radiusX; } } diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 4c9f6c06db..d08e2f2d66 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -27,6 +27,14 @@ public Config() } + public class MultiFramework : Config + { + public MultiFramework() => this.AddJob( + Job.Default.WithRuntime(ClrRuntime.Net472), + Job.Default.WithRuntime(CoreRuntime.Core21), + Job.Default.WithRuntime(CoreRuntime.Core31)); + } + public class ShortClr : Config { public ShortClr() => this.AddJob( diff --git a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs index 62d5806037..8f009e58f1 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.MultiFramework))] public class GaussianBlur { [Benchmark] From 0be08a2c0ae809f508875d7206007fd2da1a0d3b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 7 Dec 2020 14:26:29 +0000 Subject: [PATCH 09/10] Working version per-row --- .../Convolution2DProcessor{TPixel}.cs | 11 +- .../Convolution2DRowOperation{TPixel}.cs | 193 ++++++++++++++++++ .../Convolution/Convolution2DState.cs | 4 +- .../Convolution2PassProcessor{TPixel}.cs | 88 +------- .../ConvolutionProcessor{TPixel}.cs | 94 +++++++-- .../ConvolutionRowOperation{TPixel}.cs | 169 +++++++++++++++ .../Convolution/ConvolutionState.cs | 4 +- .../Processors/Convolution/Convolver.cs | 96 ++++++++- 8 files changed, 552 insertions(+), 107 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs create mode 100644 src/ImageSharp/Processing/Processors/Convolution/ConvolutionRowOperation{TPixel}.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 249c73e8d6..e787b3ec78 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -66,12 +66,17 @@ protected override void OnFrameApply(ImageFrame source) source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + // We use a rectangle 3x the interest width to allocate a buffer big enough + // for source and target bulk pixel conversion. + var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 3, interest.Height); + using (var map = new KernelSamplingMap(allocator)) { // Since the kernel sizes are identical we can use a single map. map.BuildSamplingOffsetMap(this.KernelY, interest); - var operation = new RowOperation( + var operation = new Convolution2DRowOperation( interest, targetPixels, source.PixelBuffer, @@ -81,9 +86,9 @@ protected override void OnFrameApply(ImageFrame source) this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows, Vector4>( this.Configuration, - interest, + operationBounds, in operation); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs new file mode 100644 index 0000000000..6528a2f851 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs @@ -0,0 +1,193 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// A implementing the logic for 2D convolution. + /// + internal readonly struct Convolution2DRowOperation : IRowOperation + where TPixel : unmanaged, IPixel + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; + private readonly DenseMatrix kernelMatrixY; + private readonly DenseMatrix kernelMatrixX; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Convolution2DRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + KernelSamplingMap map, + DenseMatrix kernelMatrixY, + DenseMatrix kernelMatrixX, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.map = map; + this.kernelMatrixY = kernelMatrixY; + this.kernelMatrixX = kernelMatrixX; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Invoke(int y, Span span) + { + if (this.preserveAlpha) + { + this.Convolve3(y, span); + } + else + { + this.Convolve4(y, span); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Convolve3(int y, Span span) + { + // Span is 3x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + Span sourceBuffer = span.Slice(0, boundsWidth); + Span targetYBuffer = span.Slice(boundsWidth, boundsWidth); + Span targetXBuffer = span.Slice(boundsWidth * 2, boundsWidth); + + var state = new Convolution2DState(in this.kernelMatrixY, in this.kernelMatrixX, this.map); + ReadOnlyKernel kernelY = state.KernelY; + ReadOnlyKernel kernelX = state.KernelX; + int row = y - this.bounds.Y; + ref int sampleRowBase = ref state.GetSampleRow(row); + + // Clear the target buffers for each row run. + targetYBuffer.Clear(); + targetXBuffer.Clear(); + ref Vector4 targetBaseY = ref MemoryMarshal.GetReference(targetYBuffer); + ref Vector4 targetBaseX = ref MemoryMarshal.GetReference(targetXBuffer); + + Span sourceRow; + for (int kY = 0; kY < kernelY.Rows; kY++) + { + // Get the precalculated source sample row for this kernel row and copy to our buffer. + int offsetY = Unsafe.Add(ref sampleRowBase, kY); + sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + + for (int x = 0; x < sourceBuffer.Length; x++) + { + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 targetY = ref Unsafe.Add(ref targetBaseY, x); + ref Vector4 targetX = ref Unsafe.Add(ref targetBaseX, x); + + for (int kX = 0; kX < kernelY.Columns; kX++) + { + int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + targetY += kernelX[kY, kX] * sample; + targetX += kernelY[kY, kX] * sample; + } + } + } + + // Now we need to combine the values and copy the original alpha values from the source row. + sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + + for (int x = 0; x < sourceRow.Length; x++) + { + ref Vector4 target = ref Unsafe.Add(ref targetBaseY, x); + Vector4 vectorY = target; + Vector4 vectorX = Unsafe.Add(ref targetBaseX, x); + + target = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; + } + + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Convolve4(int y, Span span) + { + // Span is 2x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + Span sourceBuffer = span.Slice(0, boundsWidth); + Span targetYBuffer = span.Slice(boundsWidth, boundsWidth); + Span targetXBuffer = span.Slice(boundsWidth * 2, boundsWidth); + + var state = new Convolution2DState(in this.kernelMatrixY, in this.kernelMatrixX, this.map); + ReadOnlyKernel kernelY = state.KernelY; + ReadOnlyKernel kernelX = state.KernelX; + int row = y - this.bounds.Y; + ref int sampleRowBase = ref state.GetSampleRow(row); + + // Clear the target buffers for each row run. + targetYBuffer.Clear(); + targetXBuffer.Clear(); + ref Vector4 targetBaseY = ref MemoryMarshal.GetReference(targetYBuffer); + ref Vector4 targetBaseX = ref MemoryMarshal.GetReference(targetXBuffer); + + for (int kY = 0; kY < kernelY.Rows; kY++) + { + // Get the precalculated source sample row for this kernel row and copy to our buffer. + int offsetY = Unsafe.Add(ref sampleRowBase, kY); + Span sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + + Numerics.Premultiply(sourceBuffer); + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + + for (int x = 0; x < sourceBuffer.Length; x++) + { + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 targetY = ref Unsafe.Add(ref targetBaseY, x); + ref Vector4 targetX = ref Unsafe.Add(ref targetBaseX, x); + + for (int kX = 0; kX < kernelY.Columns; kX++) + { + int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + targetY += kernelX[kY, kX] * sample; + targetX += kernelY[kY, kX] * sample; + } + } + } + + for (int x = 0; x < targetYBuffer.Length; x++) + { + ref Vector4 target = ref Unsafe.Add(ref targetBaseY, x); + Vector4 vectorY = target; + Vector4 vectorX = Unsafe.Add(ref targetBaseX, x); + + target = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + } + + Numerics.UnPremultiply(targetYBuffer); + + Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs index 9d17ebab05..218093ac4e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DState.cs @@ -44,11 +44,11 @@ public readonly ReadOnlyKernel KernelX } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleOffsetRow(int row) + 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 GetSampleOffsetColumn(int column) + public readonly ref int GetSampleColumn(int column) => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index e05892fa54..151b0ffccc 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -63,12 +63,16 @@ protected override void OnFrameApply(ImageFrame source) var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + // We use a rectangle 2x the interest width to allocate a buffer big enough + // for source and target bulk pixel conversion. + var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 2, interest.Height); + using (var mapX = new KernelSamplingMap(this.Configuration.MemoryAllocator)) { mapX.BuildSamplingOffsetMap(this.KernelX, interest); // Horizontal convolution - var horizontalOperation = new RowOperation( + var horizontalOperation = new ConvolutionRowOperation( interest, firstPassPixels, source.PixelBuffer, @@ -77,9 +81,9 @@ protected override void OnFrameApply(ImageFrame source) this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows, Vector4>( this.Configuration, - interest, + operationBounds, in horizontalOperation); } @@ -88,7 +92,7 @@ protected override void OnFrameApply(ImageFrame source) mapY.BuildSamplingOffsetMap(this.KernelY, interest); // Vertical convolution - var verticalOperation = new RowOperation( + var verticalOperation = new ConvolutionRowOperation( interest, source.PixelBuffer, firstPassPixels, @@ -97,83 +101,11 @@ protected override void OnFrameApply(ImageFrame source) this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows, Vector4>( this.Configuration, - interest, + operationBounds, in verticalOperation); } } - - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly DenseMatrix kernel; - private readonly Configuration configuration; - private readonly bool preserveAlpha; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourcePixels, - KernelSamplingMap map, - DenseMatrix kernel, - Configuration configuration, - bool preserveAlpha) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernel = kernel; - this.configuration = configuration; - this.preserveAlpha = preserveAlpha; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - - var state = new ConvolutionState(in this.kernel, this.map); - int row = y - this.bounds.Y; - - if (this.preserveAlpha) - { - for (int column = 0; column < this.bounds.Width; column++) - { - Convolver.Convolve3( - in state, - this.sourcePixels, - ref targetRowRef, - row, - column); - } - } - else - { - for (int column = 0; column < this.bounds.Width; column++) - { - Convolver.Convolve4( - in state, - this.sourcePixels, - ref targetRowRef, - row, - column); - } - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); - } - } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index d6be1dc563..924a1125bd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -57,6 +57,10 @@ protected override void OnFrameApply(ImageFrame source) source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + // We use a rectangle 2x the interest width to allocate a buffer big enough + // for source and target bulk pixel conversion. + var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 2, interest.Height); using (var map = new KernelSamplingMap(allocator)) { map.BuildSamplingOffsetMap(this.KernelXY, interest); @@ -64,7 +68,7 @@ protected override void OnFrameApply(ImageFrame source) var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows( this.Configuration, - interest, + operationBounds, in operation); } @@ -107,39 +111,93 @@ public RowOperation( [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { + // Span is 2x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + Span sourceBuffer = span.Slice(0, this.bounds.Width); + Span targetBuffer = span.Slice(this.bounds.Width); + ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); var state = new ConvolutionState(in this.kernel, this.map); int row = y - this.bounds.Y; + ref int sampleRowBase = ref state.GetSampleRow(row); if (this.preserveAlpha) { - for (int column = 0; column < this.bounds.Width; column++) + // Clear the target buffer for each row run. + targetBuffer.Clear(); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + + Span sourceRow; + for (int kY = 0; kY < state.Kernel.Rows; kY++) { - Convolver.Convolve3( - in state, - this.sourcePixels, - ref targetRowRef, - row, - column); + // Get the precalculated source sample row for this kernel row and copy to our buffer. + int offsetY = Unsafe.Add(ref sampleRowBase, kY); + sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + + for (int x = 0; x < sourceBuffer.Length; x++) + { + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + + for (int kX = 0; kX < state.Kernel.Columns; kX++) + { + int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + target += state.Kernel[kY, kX] * sample; + } + } + } + + // Now we need to copy the original alpha values from the source row. + sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + + for (int x = 0; x < sourceRow.Length; x++) + { + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; } } else { - for (int column = 0; column < this.bounds.Width; column++) + // Clear the target buffer for each row run. + targetBuffer.Clear(); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + + for (int kY = 0; kY < state.Kernel.Rows; kY++) { - Convolver.Convolve4( - in state, - this.sourcePixels, - ref targetRowRef, - row, - column); + // Get the precalculated source sample row for this kernel row and copy to our buffer. + int offsetY = Unsafe.Add(ref sampleRowBase, kY); + Span sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + + Numerics.Premultiply(sourceBuffer); + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + + for (int x = 0; x < sourceBuffer.Length; x++) + { + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + + for (int kX = 0; kX < state.Kernel.Columns; kX++) + { + int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + target += state.Kernel[kY, kX] * sample; + } + } } + + Numerics.UnPremultiply(targetBuffer); } - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionRowOperation{TPixel}.cs new file mode 100644 index 0000000000..82aecdaf7b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionRowOperation{TPixel}.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution +{ + /// + /// A implementing the logic for 1D convolution. + /// + internal readonly struct ConvolutionRowOperation : IRowOperation + where TPixel : unmanaged, IPixel + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly KernelSamplingMap map; + private readonly DenseMatrix kernelMatrix; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + KernelSamplingMap map, + DenseMatrix kernelMatrix, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.map = map; + this.kernelMatrix = kernelMatrix; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Invoke(int y, Span span) + { + if (this.preserveAlpha) + { + this.Convolve3(y, span); + } + else + { + this.Convolve4(y, span); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Convolve3(int y, Span span) + { + // Span is 2x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + Span sourceBuffer = span.Slice(0, this.bounds.Width); + Span targetBuffer = span.Slice(this.bounds.Width); + + ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + + var state = new ConvolutionState(in this.kernelMatrix, this.map); + ReadOnlyKernel kernel = state.Kernel; + int row = y - this.bounds.Y; + ref int sampleRowBase = ref state.GetSampleRow(row); + + // Clear the target buffer for each row run. + targetBuffer.Clear(); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + + Span sourceRow; + for (int kY = 0; kY < kernel.Rows; kY++) + { + // Get the precalculated source sample row for this kernel row and copy to our buffer. + int offsetY = Unsafe.Add(ref sampleRowBase, kY); + sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + + for (int x = 0; x < sourceBuffer.Length; x++) + { + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + + for (int kX = 0; kX < kernel.Columns; kX++) + { + int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + target += kernel[kY, kX] * sample; + } + } + } + + // Now we need to copy the original alpha values from the source row. + sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + + for (int x = 0; x < sourceRow.Length; x++) + { + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Convolve4(int y, Span span) + { + // Span is 2x bounds. + int boundsX = this.bounds.X; + int boundsWidth = this.bounds.Width; + Span sourceBuffer = span.Slice(0, this.bounds.Width); + Span targetBuffer = span.Slice(this.bounds.Width); + + ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + + var state = new ConvolutionState(in this.kernelMatrix, this.map); + ReadOnlyKernel kernel = state.Kernel; + int row = y - this.bounds.Y; + ref int sampleRowBase = ref state.GetSampleRow(row); + + // Clear the target buffer for each row run. + targetBuffer.Clear(); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + + for (int kY = 0; kY < kernel.Rows; kY++) + { + // Get the precalculated source sample row for this kernel row and copy to our buffer. + int offsetY = Unsafe.Add(ref sampleRowBase, kY); + Span sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + + Numerics.Premultiply(sourceBuffer); + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); + + for (int x = 0; x < sourceBuffer.Length; x++) + { + ref int sampleColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + + for (int kX = 0; kX < kernel.Columns; kX++) + { + int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + target += kernel[kY, kX] * sample; + } + } + } + + Numerics.UnPremultiply(targetBuffer); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs index 851eeec247..3f296c67df 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionState.cs @@ -35,11 +35,11 @@ public readonly ReadOnlyKernel Kernel } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ref int GetSampleOffsetRow(int row) + 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 GetSampleOffsetColumn(int column) + public readonly ref int GetSampleColumn(int column) => ref Unsafe.Add(ref MemoryMarshal.GetReference(this.columnOffsetMap), column * this.kernelWidth); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs index c23b71b330..721f7bbad1 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -100,12 +101,12 @@ public static void Convolve2DImpl( Vector4 vectorY = default; Vector4 vectorX = default; - ref int sampleOffsetRowBase = ref state.GetSampleOffsetRow(row); + ref int sampleOffsetRowBase = ref state.GetSampleRow(row); for (int y = 0; y < kernelHeight; y++) { int offsetY = Unsafe.Add(ref sampleOffsetRowBase, y); ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY)); - ref int sampleOffsetColumnBase = ref state.GetSampleOffsetColumn(column); + ref int sampleOffsetColumnBase = ref state.GetSampleColumn(column); for (int x = 0; x < kernelWidth; x++) { @@ -188,6 +189,93 @@ public static void Convolve4( target = vector; } + /// + /// Computes the sum of vectors in the span referenced by weighted + /// by the kernel weight values. + /// Using this method the convolution filter is not applied to alpha in addition + /// to the color channels. + /// + /// The convolution kernel state. + /// The source row. + /// The target row. + /// The current kernel row. + /// The interest x-bounds relative to the interest image. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvolveRow3( + in ConvolutionState state, + Span sourceRow, + Span targetRow, + int kY, + int bX) + { + ReadOnlyKernel kernel = state.Kernel; + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceRow); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetRow); + + Numerics.Premultiply(sourceRow); + + for (int x = 0; x < sourceRow.Length; x++) + { + Vector4 vector = default; + ref int sampleOffsetColumnBase = ref state.GetSampleColumn(x); + + for (int kX = 0; kX < kernel.Columns; kX++) + { + int offsetX = Unsafe.Add(ref sampleOffsetColumnBase, kX) - bX; + Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + vector += kernel[kY, kX] * sample; + } + + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + vector.W = target.W; + Numerics.UnPremultiply(ref vector); + target = vector; + } + } + + /// + /// Computes the sum of vectors in the span referenced by weighted + /// by the kernel weight values. + /// Using this method the convolution filter is applied to alpha in addition to the + /// color channels. + /// + /// The convolution kernel state. + /// The source row. + /// The target row. + /// The current kernel row. + /// The interest x-bounds relative to the interest image. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvolveRow4( + in ConvolutionState state, + Span sourceRow, + Span targetRow, + int kY, + int bX) + where TPixel : unmanaged, IPixel + { + ReadOnlyKernel kernel = state.Kernel; + + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceRow); + ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetRow); + + Numerics.Premultiply(sourceRow); + + for (int x = 0; x < sourceRow.Length; x++) + { + ref int sampleOffsetColumnBase = ref state.GetSampleColumn(x); + ref Vector4 target = ref Unsafe.Add(ref targetBase, x); + + for (int kX = 0; kX < kernel.Columns; kX++) + { + int offsetX = Unsafe.Add(ref sampleOffsetColumnBase, kX) - bX; + Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + target += kernel[kY, kX] * sample; + } + } + + Numerics.UnPremultiply(targetRow); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ConvolveImpl( in ConvolutionState state, @@ -201,12 +289,12 @@ private static void ConvolveImpl( int kernelHeight = kernel.Rows; int kernelWidth = kernel.Columns; - ref int sampleOffsetRowBase = ref state.GetSampleOffsetRow(row); + ref int sampleOffsetRowBase = ref state.GetSampleRow(row); for (int y = 0; y < kernelHeight; y++) { int offsetY = Unsafe.Add(ref sampleOffsetRowBase, y); ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY)); - ref int sampleOffsetColumnBase = ref state.GetSampleOffsetColumn(column); + ref int sampleOffsetColumnBase = ref state.GetSampleColumn(column); for (int x = 0; x < kernelWidth; x++) { From fd2ece0f2251c73de497ab605ceb249221647d09 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 7 Dec 2020 14:53:47 +0000 Subject: [PATCH 10/10] Cleanup --- .../Convolution2DProcessor{TPixel}.cs | 78 ----- .../Convolution2DRowOperation{TPixel}.cs | 36 +- .../ConvolutionRowOperation{TPixel}.cs | 38 +-- .../Processors/Convolution/Convolver.cs | 309 ------------------ 4 files changed, 34 insertions(+), 427 deletions(-) delete mode 100644 src/ImageSharp/Processing/Processors/Convolution/Convolver.cs diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index e787b3ec78..bb559019b7 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -1,10 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -94,80 +91,5 @@ protected override void OnFrameApply(ImageFrame source) Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } - - /// - /// A implementing the convolution logic for . - /// - private readonly struct RowOperation : IRowOperation - { - private readonly Rectangle bounds; - private readonly Buffer2D targetPixels; - private readonly Buffer2D sourcePixels; - private readonly KernelSamplingMap map; - private readonly DenseMatrix kernelY; - private readonly DenseMatrix kernelX; - private readonly Configuration configuration; - private readonly bool preserveAlpha; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation( - Rectangle bounds, - Buffer2D targetPixels, - Buffer2D sourcePixels, - KernelSamplingMap map, - DenseMatrix kernelY, - DenseMatrix kernelX, - Configuration configuration, - bool preserveAlpha) - { - this.bounds = bounds; - this.targetPixels = targetPixels; - this.sourcePixels = sourcePixels; - this.map = map; - this.kernelY = kernelY; - this.kernelX = kernelX; - this.configuration = configuration; - this.preserveAlpha = preserveAlpha; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y, Span span) - { - ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - - var state = new Convolution2DState(this.kernelY, this.kernelX, this.map); - int row = y - this.bounds.Y; - - if (this.preserveAlpha) - { - for (int column = 0; column < this.bounds.Width; column++) - { - Convolver.Convolve2D3( - in state, - this.sourcePixels, - ref targetRowRef, - row, - column); - } - } - else - { - for (int column = 0; column < this.bounds.Width; column++) - { - Convolver.Convolve2D4( - in state, - this.sourcePixels, - ref targetRowRef, - row, - column); - } - } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); - } - } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs index 6528a2f851..802d1809f2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs @@ -72,10 +72,7 @@ private void Convolve3(int y, Span span) Span targetXBuffer = span.Slice(boundsWidth * 2, boundsWidth); var state = new Convolution2DState(in this.kernelMatrixY, in this.kernelMatrixX, this.map); - ReadOnlyKernel kernelY = state.KernelY; - ReadOnlyKernel kernelX = state.KernelX; - int row = y - this.bounds.Y; - ref int sampleRowBase = ref state.GetSampleRow(row); + ref int sampleRowBase = ref state.GetSampleRow(y - this.bounds.Y); // Clear the target buffers for each row run. targetYBuffer.Clear(); @@ -83,12 +80,14 @@ private void Convolve3(int y, Span span) ref Vector4 targetBaseY = ref MemoryMarshal.GetReference(targetYBuffer); ref Vector4 targetBaseX = ref MemoryMarshal.GetReference(targetXBuffer); + ReadOnlyKernel kernelY = state.KernelY; + ReadOnlyKernel kernelX = state.KernelX; Span sourceRow; for (int kY = 0; kY < kernelY.Rows; kY++) { // Get the precalculated source sample row for this kernel row and copy to our buffer. - int offsetY = Unsafe.Add(ref sampleRowBase, kY); - sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + int sampleY = Unsafe.Add(ref sampleRowBase, kY); + sourceRow = this.sourcePixels.GetRowSpan(sampleY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -101,15 +100,16 @@ private void Convolve3(int y, Span span) for (int kX = 0; kX < kernelY.Columns; kX++) { - int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + int sampleX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, sampleX); targetY += kernelX[kY, kX] * sample; targetX += kernelY[kY, kX] * sample; } } } - // Now we need to combine the values and copy the original alpha values from the source row. + // Now we need to combine the values and copy the original alpha values + // from the source row. sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); @@ -130,7 +130,7 @@ private void Convolve3(int y, Span span) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Convolve4(int y, Span span) { - // Span is 2x bounds. + // Span is 3x bounds. int boundsX = this.bounds.X; int boundsWidth = this.bounds.Width; Span sourceBuffer = span.Slice(0, boundsWidth); @@ -138,10 +138,7 @@ private void Convolve4(int y, Span span) Span targetXBuffer = span.Slice(boundsWidth * 2, boundsWidth); var state = new Convolution2DState(in this.kernelMatrixY, in this.kernelMatrixX, this.map); - ReadOnlyKernel kernelY = state.KernelY; - ReadOnlyKernel kernelX = state.KernelX; - int row = y - this.bounds.Y; - ref int sampleRowBase = ref state.GetSampleRow(row); + ref int sampleRowBase = ref state.GetSampleRow(y - this.bounds.Y); // Clear the target buffers for each row run. targetYBuffer.Clear(); @@ -149,11 +146,13 @@ private void Convolve4(int y, Span span) ref Vector4 targetBaseY = ref MemoryMarshal.GetReference(targetYBuffer); ref Vector4 targetBaseX = ref MemoryMarshal.GetReference(targetXBuffer); + ReadOnlyKernel kernelY = state.KernelY; + ReadOnlyKernel kernelX = state.KernelX; for (int kY = 0; kY < kernelY.Rows; kY++) { // Get the precalculated source sample row for this kernel row and copy to our buffer. - int offsetY = Unsafe.Add(ref sampleRowBase, kY); - Span sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + int sampleY = Unsafe.Add(ref sampleRowBase, kY); + Span sourceRow = this.sourcePixels.GetRowSpan(sampleY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); Numerics.Premultiply(sourceBuffer); @@ -167,14 +166,15 @@ private void Convolve4(int y, Span span) for (int kX = 0; kX < kernelY.Columns; kX++) { - int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + int sampleX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, sampleX); targetY += kernelX[kY, kX] * sample; targetX += kernelY[kY, kX] * sample; } } } + // Now we need to combine the values for (int x = 0; x < targetYBuffer.Length; x++) { ref Vector4 target = ref Unsafe.Add(ref targetBaseY, x); diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionRowOperation{TPixel}.cs index 82aecdaf7b..9876b2885b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionRowOperation{TPixel}.cs @@ -67,24 +67,20 @@ private void Convolve3(int y, Span span) Span sourceBuffer = span.Slice(0, this.bounds.Width); Span targetBuffer = span.Slice(this.bounds.Width); - ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); - var state = new ConvolutionState(in this.kernelMatrix, this.map); - ReadOnlyKernel kernel = state.Kernel; - int row = y - this.bounds.Y; - ref int sampleRowBase = ref state.GetSampleRow(row); + ref int sampleRowBase = ref state.GetSampleRow(y - this.bounds.Y); // Clear the target buffer for each row run. targetBuffer.Clear(); ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + ReadOnlyKernel kernel = state.Kernel; Span sourceRow; for (int kY = 0; kY < kernel.Rows; kY++) { // Get the precalculated source sample row for this kernel row and copy to our buffer. - int offsetY = Unsafe.Add(ref sampleRowBase, kY); - sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + int sampleY = Unsafe.Add(ref sampleRowBase, kY); + sourceRow = this.sourcePixels.GetRowSpan(sampleY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -96,8 +92,8 @@ private void Convolve3(int y, Span span) for (int kX = 0; kX < kernel.Columns; kX++) { - int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + int sampleX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, sampleX); target += kernel[kY, kX] * sample; } } @@ -113,7 +109,8 @@ private void Convolve3(int y, Span span) target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; } - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); + Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -125,23 +122,19 @@ private void Convolve4(int y, Span span) Span sourceBuffer = span.Slice(0, this.bounds.Width); Span targetBuffer = span.Slice(this.bounds.Width); - ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); - var state = new ConvolutionState(in this.kernelMatrix, this.map); - ReadOnlyKernel kernel = state.Kernel; - int row = y - this.bounds.Y; - ref int sampleRowBase = ref state.GetSampleRow(row); + ref int sampleRowBase = ref state.GetSampleRow(y - this.bounds.Y); // Clear the target buffer for each row run. targetBuffer.Clear(); ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); + ReadOnlyKernel kernel = state.Kernel; for (int kY = 0; kY < kernel.Rows; kY++) { // Get the precalculated source sample row for this kernel row and copy to our buffer. - int offsetY = Unsafe.Add(ref sampleRowBase, kY); - Span sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + int sampleY = Unsafe.Add(ref sampleRowBase, kY); + Span sourceRow = this.sourcePixels.GetRowSpan(sampleY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); Numerics.Premultiply(sourceBuffer); @@ -154,8 +147,8 @@ private void Convolve4(int y, Span span) for (int kX = 0; kX < kernel.Columns; kX++) { - int offsetX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; - Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); + int sampleX = Unsafe.Add(ref sampleColumnBase, kX) - boundsX; + Vector4 sample = Unsafe.Add(ref sourceBase, sampleX); target += kernel[kY, kX] * sample; } } @@ -163,7 +156,8 @@ private void Convolve4(int y, Span span) Numerics.UnPremultiply(targetBuffer); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); + Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs deleted file mode 100644 index 721f7bbad1..0000000000 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolver.cs +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Convolution; - -namespace SixLabors.ImageSharp -{ - /// - /// Provides methods to perform convolution operations. - /// - internal static class Convolver - { - /// - /// Computes the sum of vectors in the span referenced by weighted by the two kernel weight values. - /// Using this method the convolution filter is not applied to alpha in addition to the color channels. - /// - /// The pixel format. - /// The 2D convolution kernels state. - /// The source frame. - /// The target row base reference. - /// The current row. - /// The current column. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Convolve2D3( - in Convolution2DState state, - Buffer2D sourcePixels, - ref Vector4 targetRowRef, - int row, - int column) - where TPixel : unmanaged, IPixel - { - Vector4 vector = default; - - Convolve2DImpl( - in state, - sourcePixels, - row, - column, - ref vector); - - ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - vector.W = target.W; - - Numerics.UnPremultiply(ref vector); - target = vector; - } - - /// - /// Computes the sum of vectors in the span referenced by weighted by the two kernel weight values. - /// Using this method the convolution filter is applied to alpha in addition to the color channels. - /// - /// The pixel format. - /// The 2D convolution kernels state. - /// The source frame. - /// The target row base reference. - /// The current row. - /// The current column. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Convolve2D4( - in Convolution2DState state, - Buffer2D sourcePixels, - ref Vector4 targetRowRef, - int row, - int column) - where TPixel : unmanaged, IPixel - { - Vector4 vector = default; - - Convolve2DImpl( - in state, - sourcePixels, - row, - column, - ref vector); - - ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - Numerics.UnPremultiply(ref vector); - target = vector; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Convolve2DImpl( - in Convolution2DState state, - Buffer2D sourcePixels, - int row, - int column, - ref Vector4 targetVector) - where TPixel : unmanaged, IPixel - { - ReadOnlyKernel kernelY = state.KernelY; - ReadOnlyKernel kernelX = state.KernelX; - int kernelHeight = kernelY.Rows; - int kernelWidth = kernelY.Columns; - - Vector4 vectorY = default; - Vector4 vectorX = default; - - ref int sampleOffsetRowBase = ref state.GetSampleRow(row); - for (int y = 0; y < kernelHeight; y++) - { - int offsetY = Unsafe.Add(ref sampleOffsetRowBase, y); - ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY)); - ref int sampleOffsetColumnBase = ref state.GetSampleColumn(column); - - for (int x = 0; x < kernelWidth; x++) - { - int offsetX = Unsafe.Add(ref sampleOffsetColumnBase, x); - var sample = Unsafe.Add(ref sourceRowBase, offsetX).ToVector4(); - Numerics.Premultiply(ref sample); - vectorX += kernelX[y, x] * sample; - vectorY += kernelY[y, x] * sample; - } - } - - targetVector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); - } - - /// - /// Computes the sum of vectors in the span referenced by weighted by the kernel weight values. - /// Using this method the convolution filter is not applied to alpha in addition to the color channels. - /// - /// The pixel format. - /// The convolution kernel state. - /// The source frame. - /// The target row base reference. - /// The current row. - /// The current column. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Convolve3( - in ConvolutionState state, - Buffer2D sourcePixels, - ref Vector4 targetRowRef, - int row, - int column) - where TPixel : unmanaged, IPixel - { - Vector4 vector = default; - - ConvolveImpl( - state, - sourcePixels, - row, - column, - ref vector); - - ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - vector.W = target.W; - - Numerics.UnPremultiply(ref vector); - target = vector; - } - - /// - /// Computes the sum of vectors in the span referenced by weighted by the kernel weight values. - /// Using this method the convolution filter is applied to alpha in addition to the color channels. - /// - /// The pixel format. - /// The convolution kernel state. - /// The source frame. - /// The target row base reference. - /// The current row. - /// The current column. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Convolve4( - in ConvolutionState state, - Buffer2D sourcePixels, - ref Vector4 targetRowRef, - int row, - int column) - where TPixel : unmanaged, IPixel - { - Vector4 vector = default; - - ConvolveImpl( - state, - sourcePixels, - row, - column, - ref vector); - - ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - Numerics.UnPremultiply(ref vector); - target = vector; - } - - /// - /// Computes the sum of vectors in the span referenced by weighted - /// by the kernel weight values. - /// Using this method the convolution filter is not applied to alpha in addition - /// to the color channels. - /// - /// The convolution kernel state. - /// The source row. - /// The target row. - /// The current kernel row. - /// The interest x-bounds relative to the interest image. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvolveRow3( - in ConvolutionState state, - Span sourceRow, - Span targetRow, - int kY, - int bX) - { - ReadOnlyKernel kernel = state.Kernel; - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceRow); - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetRow); - - Numerics.Premultiply(sourceRow); - - for (int x = 0; x < sourceRow.Length; x++) - { - Vector4 vector = default; - ref int sampleOffsetColumnBase = ref state.GetSampleColumn(x); - - for (int kX = 0; kX < kernel.Columns; kX++) - { - int offsetX = Unsafe.Add(ref sampleOffsetColumnBase, kX) - bX; - Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); - vector += kernel[kY, kX] * sample; - } - - ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - vector.W = target.W; - Numerics.UnPremultiply(ref vector); - target = vector; - } - } - - /// - /// Computes the sum of vectors in the span referenced by weighted - /// by the kernel weight values. - /// Using this method the convolution filter is applied to alpha in addition to the - /// color channels. - /// - /// The convolution kernel state. - /// The source row. - /// The target row. - /// The current kernel row. - /// The interest x-bounds relative to the interest image. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ConvolveRow4( - in ConvolutionState state, - Span sourceRow, - Span targetRow, - int kY, - int bX) - where TPixel : unmanaged, IPixel - { - ReadOnlyKernel kernel = state.Kernel; - - ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceRow); - ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetRow); - - Numerics.Premultiply(sourceRow); - - for (int x = 0; x < sourceRow.Length; x++) - { - ref int sampleOffsetColumnBase = ref state.GetSampleColumn(x); - ref Vector4 target = ref Unsafe.Add(ref targetBase, x); - - for (int kX = 0; kX < kernel.Columns; kX++) - { - int offsetX = Unsafe.Add(ref sampleOffsetColumnBase, kX) - bX; - Vector4 sample = Unsafe.Add(ref sourceBase, offsetX); - target += kernel[kY, kX] * sample; - } - } - - Numerics.UnPremultiply(targetRow); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ConvolveImpl( - in ConvolutionState state, - Buffer2D sourcePixels, - int row, - int column, - ref Vector4 targetVector) - where TPixel : unmanaged, IPixel - { - ReadOnlyKernel kernel = state.Kernel; - int kernelHeight = kernel.Rows; - int kernelWidth = kernel.Columns; - - ref int sampleOffsetRowBase = ref state.GetSampleRow(row); - for (int y = 0; y < kernelHeight; y++) - { - int offsetY = Unsafe.Add(ref sampleOffsetRowBase, y); - ref TPixel sourceRowBase = ref MemoryMarshal.GetReference(sourcePixels.GetRowSpan(offsetY)); - ref int sampleOffsetColumnBase = ref state.GetSampleColumn(column); - - for (int x = 0; x < kernelWidth; x++) - { - int offsetX = Unsafe.Add(ref sampleOffsetColumnBase, x); - var sample = Unsafe.Add(ref sourceRowBase, offsetX).ToVector4(); - Numerics.Premultiply(ref sample); - targetVector += kernel[y, x] * sample; - } - } - } - } -}