From d42d8df88d72c2b76199ea36ebd0a088c26395bf Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 26 Sep 2022 20:52:10 +0200 Subject: [PATCH 1/4] Introduce GetRequiredBufferLength method --- .../Advanced/IRowIntervalOperation{TBuffer}.cs | 7 +++++++ .../Advanced/IRowOperation{TBuffer}.cs | 7 +++++++ .../Advanced/ParallelRowIterator.Wrappers.cs | 16 ++++++++-------- src/ImageSharp/Advanced/ParallelRowIterator.cs | 10 ++++++---- .../AdaptiveThresholdProcessor{TPixel}.cs | 5 +++++ .../BinaryThresholdProcessor{TPixel}.cs | 5 +++++ .../Convolution/BokehBlurProcessor{TPixel}.cs | 17 +++++++++++++++++ .../Convolution2DRowOperation{TPixel}.cs | 5 +++++ .../Convolution2PassProcessor{TPixel}.cs | 10 ++++++++++ .../Convolution/ConvolutionProcessor{TPixel}.cs | 5 +++++ .../Convolution/MedianRowOperation{TPixel}.cs | 5 +++++ ...xelRowDelegateProcessor{TPixel,TDelegate}.cs | 5 +++++ .../Filters/FilterProcessor{TPixel}.cs | 5 +++++ .../Filters/OpaqueProcessor{TPixel}.cs | 5 +++++ .../Overlays/GlowProcessor{TPixel}.cs | 5 +++++ .../Overlays/VignetteProcessor{TPixel}.cs | 5 +++++ .../Linear/AffineTransformProcessor{TPixel}.cs | 5 +++++ .../ProjectiveTransformProcessor{TPixel}.cs | 5 +++++ .../Helpers/ParallelRowIteratorTests.cs | 6 ++++++ .../TestUtilities/TestImageExtensions.cs | 3 +++ 20 files changed, 124 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs index 3d61eb7333..ef8ddb3137 100644 --- a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs @@ -12,6 +12,13 @@ namespace SixLabors.ImageSharp.Advanced; public interface IRowIntervalOperation where TBuffer : unmanaged { + /// + /// Return the minimal required number of items in the buffer passed on . + /// + /// The bounds of the operation. + /// The required buffer length. + int GetRequiredBufferLength(Rectangle bounds); + /// /// Invokes the method passing the row interval and a buffer. /// diff --git a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs index 3b6a3eb0c5..8b46fc5c31 100644 --- a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs +++ b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs @@ -10,6 +10,13 @@ namespace SixLabors.ImageSharp.Advanced; public interface IRowOperation where TBuffer : unmanaged { + /// + /// Return the minimal required number of items in the buffer passed on . + /// + /// The bounds of the operation. + /// The required buffer length. + int GetRequiredBufferLength(Rectangle bounds); + /// /// Invokes the method passing the row and a buffer. /// diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs index 9e5099b893..9629b0097e 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -63,7 +63,7 @@ public static partial class ParallelRowIterator private readonly int minY; private readonly int maxY; private readonly int stepY; - private readonly int width; + private readonly int bufferLength; private readonly MemoryAllocator allocator; private readonly T action; @@ -72,14 +72,14 @@ public static partial class ParallelRowIterator int minY, int maxY, int stepY, - int width, + int bufferLength, MemoryAllocator allocator, in T action) { this.minY = minY; this.maxY = maxY; this.stepY = stepY; - this.width = width; + this.bufferLength = bufferLength; this.allocator = allocator; this.action = action; } @@ -96,7 +96,7 @@ public static partial class ParallelRowIterator int yMax = Math.Min(yMin + this.stepY, this.maxY); - using IMemoryOwner buffer = this.allocator.Allocate(this.width); + using IMemoryOwner buffer = this.allocator.Allocate(this.bufferLength); Span span = buffer.Memory.Span; @@ -153,7 +153,7 @@ public static partial class ParallelRowIterator private readonly int minY; private readonly int maxY; private readonly int stepY; - private readonly int width; + private readonly int bufferLength; private readonly MemoryAllocator allocator; private readonly T operation; @@ -162,14 +162,14 @@ public static partial class ParallelRowIterator int minY, int maxY, int stepY, - int width, + int bufferLength, MemoryAllocator allocator, in T operation) { this.minY = minY; this.maxY = maxY; this.stepY = stepY; - this.width = width; + this.bufferLength = bufferLength; this.allocator = allocator; this.operation = operation; } @@ -187,7 +187,7 @@ public static partial class ParallelRowIterator int yMax = Math.Min(yMin + this.stepY, this.maxY); var rows = new RowInterval(yMin, yMax); - using IMemoryOwner buffer = this.allocator.Allocate(this.width); + using IMemoryOwner buffer = this.allocator.Allocate(this.bufferLength); Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span); } diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs index 21736b71f0..0eb5952a63 100644 --- a/src/ImageSharp/Advanced/ParallelRowIterator.cs +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -118,11 +118,12 @@ public static partial class ParallelRowIterator int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); MemoryAllocator allocator = parallelSettings.MemoryAllocator; + int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle); // Avoid TPL overhead in this trivial case: if (numOfSteps == 1) { - using IMemoryOwner buffer = allocator.Allocate(width); + using IMemoryOwner buffer = allocator.Allocate(bufferLength); Span span = buffer.Memory.Span; for (int y = top; y < bottom; y++) @@ -135,7 +136,7 @@ public static partial class ParallelRowIterator int verticalStep = DivideCeil(height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); + var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, bufferLength, allocator, in operation); Parallel.For( 0, @@ -244,12 +245,13 @@ public static partial class ParallelRowIterator int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); MemoryAllocator allocator = parallelSettings.MemoryAllocator; + int bufferLength = Unsafe.AsRef(operation).GetRequiredBufferLength(rectangle); // Avoid TPL overhead in this trivial case: if (numOfSteps == 1) { var rows = new RowInterval(top, bottom); - using IMemoryOwner buffer = allocator.Allocate(width); + using IMemoryOwner buffer = allocator.Allocate(bufferLength); Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span); @@ -258,7 +260,7 @@ public static partial class ParallelRowIterator int verticalStep = DivideCeil(height, numOfSteps); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); + var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, bufferLength, allocator, in operation); Parallel.For( 0, diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs index e3b2025aa5..73c7c3302d 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs @@ -85,6 +85,11 @@ internal class AdaptiveThresholdProcessor : ImageProcessor this.clusterSize = clusterSize; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 1a35973fbd..b710243a56 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -86,6 +86,11 @@ internal class BinaryThresholdProcessor : ImageProcessor this.configuration = configuration; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Invoke(int y, Span span) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 7e7cf81385..55c03abe6a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -220,6 +220,11 @@ internal class BokehBlurProcessor : ImageProcessor this.configuration = configuration; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) @@ -289,6 +294,11 @@ internal class BokehBlurProcessor : ImageProcessor this.gamma = gamma; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) @@ -329,6 +339,13 @@ internal class BokehBlurProcessor : ImageProcessor this.configuration = configuration; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + { + return bounds.Width; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs index caeb225731..c04581444f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs @@ -46,6 +46,11 @@ internal readonly struct Convolution2DRowOperation : IRowOperation + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Invoke(int y, Span span) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 7aa83b0dd0..fa05a98fd1 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -143,6 +143,11 @@ internal class Convolution2PassProcessor : ImageProcessor this.preserveAlpha = preserveAlpha; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Invoke(int y, Span span) @@ -304,6 +309,11 @@ internal class Convolution2PassProcessor : ImageProcessor this.preserveAlpha = preserveAlpha; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Invoke(int y, Span span) diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index d7a8f743c7..f20cca3f71 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -106,6 +106,11 @@ internal class ConvolutionProcessor : ImageProcessor this.preserveAlpha = preserveAlpha; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs index aff26865fb..17df7b9be4 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs @@ -43,6 +43,11 @@ internal readonly struct MedianRowOperation : IRowOperation this.wChannelStart = this.zChannelStart + kernelCount; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + public void Invoke(int y, Span span) { // Span has kernelSize^2 followed by bound width. diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index e8c7468911..f59b95050e 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -83,6 +83,11 @@ internal sealed class PixelRowDelegateProcessor : ImageProces this.rowProcessor = rowProcessor; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index e61b528efb..5ad245e3ce 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -66,6 +66,11 @@ internal class FilterProcessor : ImageProcessor this.configuration = configuration; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) diff --git a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs index 30ffa7899d..4b8fd9056e 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs @@ -46,6 +46,11 @@ internal sealed class OpaqueProcessor : ImageProcessor this.bounds = bounds; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index c431650b33..19ce6c417d 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -93,6 +93,11 @@ internal class GlowProcessor : ImageProcessor this.source = source; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index c69b6360d5..a327deec1c 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -101,6 +101,11 @@ internal class VignetteProcessor : ImageProcessor this.source = source; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 8add73d33c..bed7dcccb7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -176,6 +176,11 @@ internal class AffineTransformProcessor : TransformProcessor, IR this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Width, destination.Width); } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows, Span span) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index 440becf833..14236e3c2b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -176,6 +176,11 @@ internal class ProjectiveTransformProcessor : TransformProcessor this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Width, destination.Width); } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(in RowInterval rows, Span span) { diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index e77d8fee42..1700b4a734 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -413,6 +413,9 @@ public class ParallelRowIteratorTests public TestRowIntervalOperation(Action action) => this.action = action; + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + public void Invoke(in RowInterval rows) => this.action(rows); } @@ -424,6 +427,9 @@ public class ParallelRowIteratorTests public TestRowIntervalOperation(RowIntervalAction action) => this.action = action; + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + public void Invoke(in RowInterval rows, Span span) => this.action(rows, span); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 5032f8de58..0201848350 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -755,6 +755,9 @@ public static class TestImageExtensions this.source = source; } + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + public void Invoke(in RowInterval rows, Span span) { for (int y = rows.Min; y < rows.Max; y++) From 05588d5d344292c8963bae1a5f6e55ad49c7df88 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 26 Sep 2022 22:01:21 +0200 Subject: [PATCH 2/4] Buffer length defined by RowOperation struct --- .../Convolution/Convolution2DProcessor{TPixel}.cs | 6 +----- .../Convolution/Convolution2DRowOperation{TPixel}.cs | 2 +- .../Convolution/Convolution2PassProcessor{TPixel}.cs | 12 ++++-------- .../Convolution/ConvolutionProcessor{TPixel}.cs | 7 ++----- .../Convolution/MedianBlurProcessor{TPixel}.cs | 7 +------ .../Convolution/MedianRowOperation{TPixel}.cs | 4 ++-- 6 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index b8a61e5a32..8a7c424815 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -64,10 +64,6 @@ internal class Convolution2DProcessor : ImageProcessor 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. @@ -85,7 +81,7 @@ internal class Convolution2DProcessor : ImageProcessor ParallelRowIterator.IterateRows, Vector4>( this.Configuration, - operationBounds, + interest, in operation); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs index c04581444f..e5963bd390 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs @@ -49,7 +49,7 @@ internal readonly struct Convolution2DRowOperation : IRowOperation [MethodImpl(InliningOptions.ShortMethod)] public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; + => 3 * bounds.Width; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index fa05a98fd1..094a96f787 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -70,10 +70,6 @@ internal class Convolution2PassProcessor : ImageProcessor 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); - // We can create a single sampling map with the size as if we were using the non separated 2D kernel // the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur. using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator); @@ -92,7 +88,7 @@ internal class Convolution2PassProcessor : ImageProcessor ParallelRowIterator.IterateRows( this.Configuration, - operationBounds, + interest, in horizontalOperation); // Vertical convolution @@ -107,7 +103,7 @@ internal class Convolution2PassProcessor : ImageProcessor ParallelRowIterator.IterateRows( this.Configuration, - operationBounds, + interest, in verticalOperation); } @@ -146,7 +142,7 @@ internal class Convolution2PassProcessor : ImageProcessor /// [MethodImpl(InliningOptions.ShortMethod)] public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; + => 2 * bounds.Width; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -312,7 +308,7 @@ internal class Convolution2PassProcessor : ImageProcessor /// [MethodImpl(InliningOptions.ShortMethod)] public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; + => 2 * bounds.Width; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index f20cca3f71..54dad64a6b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -57,9 +57,6 @@ internal class ConvolutionProcessor : ImageProcessor 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); @@ -67,7 +64,7 @@ internal class ConvolutionProcessor : ImageProcessor var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows( this.Configuration, - operationBounds, + interest, in operation); } @@ -109,7 +106,7 @@ internal class ConvolutionProcessor : ImageProcessor /// [MethodImpl(InliningOptions.ShortMethod)] public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; + => 2 * bounds.Width; /// [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs index 4f0c2a36c9..fe3a29d437 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs @@ -31,11 +31,6 @@ internal sealed class MedianBlurProcessor : ImageProcessor Rectangle interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - // We use a rectangle with width set wider, to allocate a buffer big enough - // for kernel source, channel buffers, source rows and target bulk pixel conversion. - int operationWidth = (2 * kernelSize * kernelSize) + interest.Width + (kernelSize * interest.Width); - Rectangle operationBounds = new(interest.X, interest.Y, operationWidth, interest.Height); - using KernelSamplingMap map = new(this.Configuration.MemoryAllocator); map.BuildSamplingOffsetMap(kernelSize, kernelSize, interest, this.definition.BorderWrapModeX, this.definition.BorderWrapModeY); @@ -50,7 +45,7 @@ internal sealed class MedianBlurProcessor : ImageProcessor ParallelRowIterator.IterateRows, Vector4>( this.Configuration, - operationBounds, + interest, in operation); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs index 17df7b9be4..8caf33440d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs @@ -46,11 +46,11 @@ internal readonly struct MedianRowOperation : IRowOperation /// [MethodImpl(InliningOptions.ShortMethod)] public int GetRequiredBufferLength(Rectangle bounds) - => bounds.Width; + => (2 * this.kernelSize * this.kernelSize) + bounds.Width + (kernelSize * bounds.Width); public void Invoke(int y, Span span) { - // Span has kernelSize^2 followed by bound width. + // Span has kernelSize^2 twice, then bound width followed by kernelsize * bounds width. int boundsX = this.bounds.X; int boundsWidth = this.bounds.Width; int kernelCount = this.kernelSize * this.kernelSize; From 1ec6da892ab1d28fc2f14e348c90575cb802cf76 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 26 Sep 2022 22:01:35 +0200 Subject: [PATCH 3/4] Bulk pixel conversion --- .../AutoLevelProcessor{TPixel}.cs | 62 +++++++++++++------ ...lHistogramEqualizationProcessor{TPixel}.cs | 33 ++++++---- .../GrayscaleLevelsRowOperation{TPixel}.cs | 23 +++++-- 3 files changed, 81 insertions(+), 37 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs index 856fba3dcb..c07ac3aa34 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs @@ -59,8 +59,8 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); // Build the histogram of the grayscale levels. - var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); - ParallelRowIterator.IterateRows( + var grayscaleOperation = new GrayscaleLevelsRowOperation(this.Configuration, interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); + ParallelRowIterator.IterateRows, Vector4>( this.Configuration, interest, in grayscaleOperation); @@ -83,16 +83,16 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor( this.Configuration, interest, in cdfOperation); } else { - var cdfOperation = new SeperateChannelsRowOperation(interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); - ParallelRowIterator.IterateRows( + var cdfOperation = new SeperateChannelsRowOperation(this.Configuration, interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); + ParallelRowIterator.IterateRows( this.Configuration, interest, in cdfOperation); @@ -102,8 +102,9 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor /// A implementing the cdf logic for synchronized color channels. /// - private readonly struct SynchronizedChannelsRowOperation : IRowOperation + private readonly struct SynchronizedChannelsRowOperation : IRowOperation { + private readonly Configuration configuration; private readonly Rectangle bounds; private readonly IMemoryOwner cdfBuffer; private readonly Buffer2D source; @@ -112,12 +113,14 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor cdfBuffer, Buffer2D source, int luminanceLevels, float numberOfPixelsMinusCdfMin) { + this.configuration = configuration; this.bounds = bounds; this.cdfBuffer = cdfBuffer; this.source = source; @@ -127,33 +130,42 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { + Span vectorBuffer = span.Slice(0, this.bounds.Width); + ref Vector4 vectorRef = ref MemoryMarshal.GetReference(vectorBuffer); ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); var sourceAccess = new PixelAccessor(this.source); - Span pixelRow = sourceAccess.GetRowSpan(y); int levels = this.luminanceLevels; float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; + Span pixelRow = sourceAccess.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + for (int x = 0; x < this.bounds.Width; x++) { - // TODO: We should bulk convert here. - ref TPixel pixel = ref pixelRow[x]; - var vector = pixel.ToVector4(); + var vector = Unsafe.Add(ref vectorRef, x); int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); float scaledLuminance = Unsafe.Add(ref cdfBase, luminance) / noOfPixelsMinusCdfMin; float scalingFactor = scaledLuminance * levels / luminance; Vector4 scaledVector = new Vector4(scalingFactor * vector.X, scalingFactor * vector.Y, scalingFactor * vector.Z, vector.W); - pixel.FromVector4(scaledVector); + Unsafe.Add(ref vectorRef, x) = scaledVector; } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); } } /// /// A implementing the cdf logic for separate color channels. /// - private readonly struct SeperateChannelsRowOperation : IRowOperation + private readonly struct SeperateChannelsRowOperation : IRowOperation { + private readonly Configuration configuration; private readonly Rectangle bounds; private readonly IMemoryOwner cdfBuffer; private readonly Buffer2D source; @@ -162,12 +174,14 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor cdfBuffer, Buffer2D source, int luminanceLevels, float numberOfPixelsMinusCdfMin) { + this.configuration = configuration; this.bounds = bounds; this.cdfBuffer = cdfBuffer; this.source = source; @@ -177,19 +191,25 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { + Span vectorBuffer = span.Slice(0, this.bounds.Width); + ref Vector4 vectorRef = ref MemoryMarshal.GetReference(vectorBuffer); ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); var sourceAccess = new PixelAccessor(this.source); - Span pixelRow = sourceAccess.GetRowSpan(y); int levelsMinusOne = this.luminanceLevels - 1; float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; + Span pixelRow = sourceAccess.GetRowSpan(y); + PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + for (int x = 0; x < this.bounds.Width; x++) { - // TODO: We should bulk convert here. - ref TPixel pixel = ref pixelRow[x]; - var vector = pixel.ToVector4() * levelsMinusOne; + var vector = Unsafe.Add(ref vectorRef, x) * levelsMinusOne; uint originalX = (uint)MathF.Round(vector.X); float scaledX = Unsafe.Add(ref cdfBase, originalX) / noOfPixelsMinusCdfMin; @@ -197,8 +217,10 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index d506777be0..7e9e064642 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -51,8 +51,8 @@ internal class GlobalHistogramEqualizationProcessor : HistogramEqualizat using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); // Build the histogram of the grayscale levels. - var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); - ParallelRowIterator.IterateRows( + var grayscaleOperation = new GrayscaleLevelsRowOperation(this.Configuration, interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); + ParallelRowIterator.IterateRows, Vector4>( this.Configuration, interest, in grayscaleOperation); @@ -74,8 +74,8 @@ internal class GlobalHistogramEqualizationProcessor : HistogramEqualizat float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image - var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); - ParallelRowIterator.IterateRows( + var cdfOperation = new CdfApplicationRowOperation(this.Configuration, interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); + ParallelRowIterator.IterateRows( this.Configuration, interest, in cdfOperation); @@ -84,8 +84,9 @@ internal class GlobalHistogramEqualizationProcessor : HistogramEqualizat /// /// A implementing the cdf application levels logic for . /// - private readonly struct CdfApplicationRowOperation : IRowOperation + private readonly struct CdfApplicationRowOperation : IRowOperation { + private readonly Configuration configuration; private readonly Rectangle bounds; private readonly IMemoryOwner cdfBuffer; private readonly Buffer2D source; @@ -94,12 +95,14 @@ internal class GlobalHistogramEqualizationProcessor : HistogramEqualizat [MethodImpl(InliningOptions.ShortMethod)] public CdfApplicationRowOperation( + Configuration configuration, Rectangle bounds, IMemoryOwner cdfBuffer, Buffer2D source, int luminanceLevels, float numberOfPixelsMinusCdfMin) { + this.configuration = configuration; this.bounds = bounds; this.cdfBuffer = cdfBuffer; this.source = source; @@ -109,22 +112,30 @@ internal class GlobalHistogramEqualizationProcessor : HistogramEqualizat /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { + Span vectorBuffer = span.Slice(0, this.bounds.Width); + ref Vector4 vectorRef = ref MemoryMarshal.GetReference(vectorBuffer); ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); - Span pixelRow = this.source.DangerousGetRowSpan(y); int levels = this.luminanceLevels; float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; + Span pixelRow = this.source.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + for (int x = 0; x < this.bounds.Width; x++) { - // TODO: We should bulk convert here. - ref TPixel pixel = ref pixelRow[x]; - var vector = pixel.ToVector4(); + var vector = Unsafe.Add(ref vectorRef, x); int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / noOfPixelsMinusCdfMin; - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, vector.W)); + Unsafe.Add(ref vectorRef, x) = new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, vector.W); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs index f4fcd15782..8895fdc612 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; @@ -11,11 +12,12 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Normalization; /// -/// A implementing the grayscale levels logic as . +/// A implementing the grayscale levels logic as . /// -internal readonly struct GrayscaleLevelsRowOperation : IRowOperation +internal readonly struct GrayscaleLevelsRowOperation : IRowOperation where TPixel : unmanaged, IPixel { + private readonly Configuration configuration; private readonly Rectangle bounds; private readonly IMemoryOwner histogramBuffer; private readonly Buffer2D source; @@ -23,11 +25,13 @@ internal readonly struct GrayscaleLevelsRowOperation : IRowOperation [MethodImpl(InliningOptions.ShortMethod)] public GrayscaleLevelsRowOperation( + Configuration configuration, Rectangle bounds, IMemoryOwner histogramBuffer, Buffer2D source, int luminanceLevels) { + this.configuration = configuration; this.bounds = bounds; this.histogramBuffer = histogramBuffer; this.source = source; @@ -36,16 +40,23 @@ internal readonly struct GrayscaleLevelsRowOperation : IRowOperation /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { + Span vectorBuffer = span.Slice(0, this.bounds.Width); + ref Vector4 vectorRef = ref MemoryMarshal.GetReference(vectorBuffer); ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); - Span pixelRow = this.source.DangerousGetRowSpan(y); int levels = this.luminanceLevels; + Span pixelRow = this.source.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + for (int x = 0; x < this.bounds.Width; x++) { - // TODO: We should bulk convert here. - var vector = pixelRow[x].ToVector4(); + var vector = Unsafe.Add(ref vectorRef, x); int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); Interlocked.Increment(ref Unsafe.Add(ref histogramBase, luminance)); } From ec79e87715751816e4040053c7e88a7620e08021 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 26 Sep 2022 22:18:16 +0200 Subject: [PATCH 4/4] Fix SA1101 --- .../Processors/Convolution/MedianRowOperation{TPixel}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs index 8caf33440d..dbb62f0c58 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs @@ -46,7 +46,7 @@ internal readonly struct MedianRowOperation : IRowOperation /// [MethodImpl(InliningOptions.ShortMethod)] public int GetRequiredBufferLength(Rectangle bounds) - => (2 * this.kernelSize * this.kernelSize) + bounds.Width + (kernelSize * bounds.Width); + => (2 * this.kernelSize * this.kernelSize) + bounds.Width + (this.kernelSize * bounds.Width); public void Invoke(int y, Span span) {