diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs index 3d61eb733..ef8ddb313 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 3b6a3eb0c..8b46fc5c3 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 9e5099b89..9629b0097 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 21736b71f..0eb5952a6 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 e3b2025aa..73c7c3302 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 1a35973fb..b710243a5 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 7e7cf8138..55c03abe6 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 caeb22573..c04581444 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 7aa83b0dd..fa05a98fd 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 d7a8f743c..f20cca3f7 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 aff26865f..17df7b9be 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 e8c746891..f59b95050 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 e61b528ef..5ad245e3c 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 30ffa7899..4b8fd9056 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 c431650b3..19ce6c417 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 c69b6360d..a327deec1 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 8add73d33..bed7dcccb 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 440becf83..14236e3c2 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 e77d8fee4..1700b4a73 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 5032f8de5..020184835 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++)