Browse Source

Merge pull request #2241 from ynse01/buffer-length-in-row-operation

Extend row operation interfaces with buffer length method
pull/2245/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
e7f701e01d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs
  2. 7
      src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs
  3. 16
      src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs
  4. 10
      src/ImageSharp/Advanced/ParallelRowIterator.cs
  5. 5
      src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs
  6. 5
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
  7. 17
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
  8. 6
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
  9. 5
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs
  10. 18
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  11. 10
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
  12. 7
      src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs
  13. 7
      src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs
  14. 5
      src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs
  15. 5
      src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs
  16. 5
      src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs
  17. 62
      src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs
  18. 33
      src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs
  19. 23
      src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs
  20. 5
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs
  21. 5
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs
  22. 5
      src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
  23. 5
      src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
  24. 6
      tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs
  25. 3
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

7
src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs

@ -12,6 +12,13 @@ namespace SixLabors.ImageSharp.Advanced;
public interface IRowIntervalOperation<TBuffer>
where TBuffer : unmanaged
{
/// <summary>
/// Return the minimal required number of items in the buffer passed on <see cref="Invoke" />.
/// </summary>
/// <param name="bounds">The bounds of the operation.</param>
/// <returns>The required buffer length.</returns>
int GetRequiredBufferLength(Rectangle bounds);
/// <summary>
/// Invokes the method passing the row interval and a buffer.
/// </summary>

7
src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs

@ -10,6 +10,13 @@ namespace SixLabors.ImageSharp.Advanced;
public interface IRowOperation<TBuffer>
where TBuffer : unmanaged
{
/// <summary>
/// Return the minimal required number of items in the buffer passed on <see cref="Invoke" />.
/// </summary>
/// <param name="bounds">The bounds of the operation.</param>
/// <returns>The required buffer length.</returns>
int GetRequiredBufferLength(Rectangle bounds);
/// <summary>
/// Invokes the method passing the row and a buffer.
/// </summary>

16
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<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.width);
using IMemoryOwner<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.bufferLength);
Span<TBuffer> 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<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.width);
using IMemoryOwner<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.bufferLength);
Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span);
}

10
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<TBuffer> buffer = allocator.Allocate<TBuffer>(width);
using IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(bufferLength);
Span<TBuffer> 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<T, TBuffer>(top, bottom, verticalStep, width, allocator, in operation);
var wrappingOperation = new RowOperationWrapper<T, TBuffer>(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<TBuffer> buffer = allocator.Allocate<TBuffer>(width);
using IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(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<T, TBuffer>(top, bottom, verticalStep, width, allocator, in operation);
var wrappingOperation = new RowIntervalOperationWrapper<T, TBuffer>(top, bottom, verticalStep, bufferLength, allocator, in operation);
Parallel.For(
0,

5
src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs

@ -85,6 +85,11 @@ internal class AdaptiveThresholdProcessor<TPixel> : ImageProcessor<TPixel>
this.clusterSize = clusterSize;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<L8> span)

5
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs

@ -86,6 +86,11 @@ internal class BinaryThresholdProcessor<TPixel> : ImageProcessor<TPixel>
this.configuration = configuration;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Rgb24> span)

17
src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs

@ -220,6 +220,11 @@ internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
this.configuration = configuration;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
@ -289,6 +294,11 @@ internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
this.gamma = gamma;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
@ -329,6 +339,13 @@ internal class BokehBlurProcessor<TPixel> : ImageProcessor<TPixel>
this.configuration = configuration;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
{
return bounds.Width;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)

6
src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs

@ -64,10 +64,6 @@ internal class Convolution2DProcessor<TPixel> : ImageProcessor<TPixel>
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<TPixel> : ImageProcessor<TPixel>
ParallelRowIterator.IterateRows<Convolution2DRowOperation<TPixel>, Vector4>(
this.Configuration,
operationBounds,
interest,
in operation);
}

5
src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs

@ -46,6 +46,11 @@ internal readonly struct Convolution2DRowOperation<TPixel> : IRowOperation<Vecto
this.preserveAlpha = preserveAlpha;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 3 * bounds.Width;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Vector4> span)

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

@ -70,10 +70,6 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
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<TPixel> : ImageProcessor<TPixel>
ParallelRowIterator.IterateRows<HorizontalConvolutionRowOperation, Vector4>(
this.Configuration,
operationBounds,
interest,
in horizontalOperation);
// Vertical convolution
@ -107,7 +103,7 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
ParallelRowIterator.IterateRows<VerticalConvolutionRowOperation, Vector4>(
this.Configuration,
operationBounds,
interest,
in verticalOperation);
}
@ -143,6 +139,11 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
this.preserveAlpha = preserveAlpha;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 2 * bounds.Width;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Vector4> span)
@ -304,6 +305,11 @@ internal class Convolution2PassProcessor<TPixel> : ImageProcessor<TPixel>
this.preserveAlpha = preserveAlpha;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 2 * bounds.Width;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invoke(int y, Span<Vector4> span)

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

@ -57,9 +57,6 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
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<TPixel> : ImageProcessor<TPixel>
var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
this.Configuration,
operationBounds,
interest,
in operation);
}
@ -106,6 +103,11 @@ internal class ConvolutionProcessor<TPixel> : ImageProcessor<TPixel>
this.preserveAlpha = preserveAlpha;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> 2 * bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)

7
src/ImageSharp/Processing/Processors/Convolution/MedianBlurProcessor{TPixel}.cs

@ -31,11 +31,6 @@ internal sealed class MedianBlurProcessor<TPixel> : ImageProcessor<TPixel>
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<TPixel> : ImageProcessor<TPixel>
ParallelRowIterator.IterateRows<MedianRowOperation<TPixel>, Vector4>(
this.Configuration,
operationBounds,
interest,
in operation);
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);

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

@ -43,9 +43,14 @@ internal readonly struct MedianRowOperation<TPixel> : IRowOperation<Vector4>
this.wChannelStart = this.zChannelStart + kernelCount;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> (2 * this.kernelSize * this.kernelSize) + bounds.Width + (this.kernelSize * bounds.Width);
public void Invoke(int y, Span<Vector4> 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;

5
src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs

@ -83,6 +83,11 @@ internal sealed class PixelRowDelegateProcessor<TPixel, TDelegate> : ImageProces
this.rowProcessor = rowProcessor;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)

5
src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs

@ -66,6 +66,11 @@ internal class FilterProcessor<TPixel> : ImageProcessor<TPixel>
this.configuration = configuration;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)

5
src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs

@ -46,6 +46,11 @@ internal sealed class OpaqueProcessor<TPixel> : ImageProcessor<TPixel>
this.bounds = bounds;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)

62
src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs

@ -59,8 +59,8 @@ internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixe
using IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean);
// Build the histogram of the grayscale levels.
var grayscaleOperation = new GrayscaleLevelsRowOperation<TPixel>(interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels);
ParallelRowIterator.IterateRows(
var grayscaleOperation = new GrayscaleLevelsRowOperation<TPixel>(this.Configuration, interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels);
ParallelRowIterator.IterateRows<GrayscaleLevelsRowOperation<TPixel>, Vector4>(
this.Configuration,
interest,
in grayscaleOperation);
@ -83,16 +83,16 @@ internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixe
if (this.SyncChannels)
{
var cdfOperation = new SynchronizedChannelsRowOperation(interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin);
ParallelRowIterator.IterateRows(
var cdfOperation = new SynchronizedChannelsRowOperation(this.Configuration, interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin);
ParallelRowIterator.IterateRows<SynchronizedChannelsRowOperation, Vector4>(
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<SeperateChannelsRowOperation, Vector4>(
this.Configuration,
interest,
in cdfOperation);
@ -102,8 +102,9 @@ internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixe
/// <summary>
/// A <see langword="struct"/> implementing the cdf logic for synchronized color channels.
/// </summary>
private readonly struct SynchronizedChannelsRowOperation : IRowOperation
private readonly struct SynchronizedChannelsRowOperation : IRowOperation<Vector4>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly IMemoryOwner<int> cdfBuffer;
private readonly Buffer2D<TPixel> source;
@ -112,12 +113,14 @@ internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixe
[MethodImpl(InliningOptions.ShortMethod)]
public SynchronizedChannelsRowOperation(
Configuration configuration,
Rectangle bounds,
IMemoryOwner<int> cdfBuffer,
Buffer2D<TPixel> source,
int luminanceLevels,
float numberOfPixelsMinusCdfMin)
{
this.configuration = configuration;
this.bounds = bounds;
this.cdfBuffer = cdfBuffer;
this.source = source;
@ -127,33 +130,42 @@ internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixe
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
{
Span<Vector4> 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<TPixel>(this.source);
Span<TPixel> pixelRow = sourceAccess.GetRowSpan(y);
int levels = this.luminanceLevels;
float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin;
Span<TPixel> pixelRow = sourceAccess.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow);
}
}
/// <summary>
/// A <see langword="struct"/> implementing the cdf logic for separate color channels.
/// </summary>
private readonly struct SeperateChannelsRowOperation : IRowOperation
private readonly struct SeperateChannelsRowOperation : IRowOperation<Vector4>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly IMemoryOwner<int> cdfBuffer;
private readonly Buffer2D<TPixel> source;
@ -162,12 +174,14 @@ internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixe
[MethodImpl(InliningOptions.ShortMethod)]
public SeperateChannelsRowOperation(
Configuration configuration,
Rectangle bounds,
IMemoryOwner<int> cdfBuffer,
Buffer2D<TPixel> source,
int luminanceLevels,
float numberOfPixelsMinusCdfMin)
{
this.configuration = configuration;
this.bounds = bounds;
this.cdfBuffer = cdfBuffer;
this.source = source;
@ -177,19 +191,25 @@ internal class AutoLevelProcessor<TPixel> : HistogramEqualizationProcessor<TPixe
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
{
Span<Vector4> 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<TPixel>(this.source);
Span<TPixel> pixelRow = sourceAccess.GetRowSpan(y);
int levelsMinusOne = this.luminanceLevels - 1;
float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin;
Span<TPixel> pixelRow = sourceAccess.GetRowSpan(y);
PixelOperations<TPixel>.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<TPixel> : HistogramEqualizationProcessor<TPixe
float scaledY = Unsafe.Add(ref cdfBase, originalY) / noOfPixelsMinusCdfMin;
uint originalZ = (uint)MathF.Round(vector.Z);
float scaledZ = Unsafe.Add(ref cdfBase, originalZ) / noOfPixelsMinusCdfMin;
pixel.FromVector4(new Vector4(scaledX, scaledY, scaledZ, vector.W));
Unsafe.Add(ref vectorRef, x) = new Vector4(scaledX, scaledY, scaledZ, vector.W);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow);
}
}
}

33
src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs

@ -51,8 +51,8 @@ internal class GlobalHistogramEqualizationProcessor<TPixel> : HistogramEqualizat
using IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean);
// Build the histogram of the grayscale levels.
var grayscaleOperation = new GrayscaleLevelsRowOperation<TPixel>(interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels);
ParallelRowIterator.IterateRows(
var grayscaleOperation = new GrayscaleLevelsRowOperation<TPixel>(this.Configuration, interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels);
ParallelRowIterator.IterateRows<GrayscaleLevelsRowOperation<TPixel>, Vector4>(
this.Configuration,
interest,
in grayscaleOperation);
@ -74,8 +74,8 @@ internal class GlobalHistogramEqualizationProcessor<TPixel> : 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<CdfApplicationRowOperation, Vector4>(
this.Configuration,
interest,
in cdfOperation);
@ -84,8 +84,9 @@ internal class GlobalHistogramEqualizationProcessor<TPixel> : HistogramEqualizat
/// <summary>
/// A <see langword="struct"/> implementing the cdf application levels logic for <see cref="GlobalHistogramEqualizationProcessor{TPixel}"/>.
/// </summary>
private readonly struct CdfApplicationRowOperation : IRowOperation
private readonly struct CdfApplicationRowOperation : IRowOperation<Vector4>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly IMemoryOwner<int> cdfBuffer;
private readonly Buffer2D<TPixel> source;
@ -94,12 +95,14 @@ internal class GlobalHistogramEqualizationProcessor<TPixel> : HistogramEqualizat
[MethodImpl(InliningOptions.ShortMethod)]
public CdfApplicationRowOperation(
Configuration configuration,
Rectangle bounds,
IMemoryOwner<int> cdfBuffer,
Buffer2D<TPixel> source,
int luminanceLevels,
float numberOfPixelsMinusCdfMin)
{
this.configuration = configuration;
this.bounds = bounds;
this.cdfBuffer = cdfBuffer;
this.source = source;
@ -109,22 +112,30 @@ internal class GlobalHistogramEqualizationProcessor<TPixel> : HistogramEqualizat
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
{
Span<Vector4> vectorBuffer = span.Slice(0, this.bounds.Width);
ref Vector4 vectorRef = ref MemoryMarshal.GetReference(vectorBuffer);
ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan());
Span<TPixel> pixelRow = this.source.DangerousGetRowSpan(y);
int levels = this.luminanceLevels;
float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin;
Span<TPixel> pixelRow = this.source.DangerousGetRowSpan(y);
PixelOperations<TPixel>.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<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow);
}
}
}

23
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;
/// <summary>
/// A <see langword="struct"/> implementing the grayscale levels logic as <see cref="IRowOperation"/>.
/// A <see langword="struct"/> implementing the grayscale levels logic as <see cref="IRowOperation{Vector4}"/>.
/// </summary>
internal readonly struct GrayscaleLevelsRowOperation<TPixel> : IRowOperation
internal readonly struct GrayscaleLevelsRowOperation<TPixel> : IRowOperation<Vector4>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly IMemoryOwner<int> histogramBuffer;
private readonly Buffer2D<TPixel> source;
@ -23,11 +25,13 @@ internal readonly struct GrayscaleLevelsRowOperation<TPixel> : IRowOperation
[MethodImpl(InliningOptions.ShortMethod)]
public GrayscaleLevelsRowOperation(
Configuration configuration,
Rectangle bounds,
IMemoryOwner<int> histogramBuffer,
Buffer2D<TPixel> source,
int luminanceLevels)
{
this.configuration = configuration;
this.bounds = bounds;
this.histogramBuffer = histogramBuffer;
this.source = source;
@ -36,16 +40,23 @@ internal readonly struct GrayscaleLevelsRowOperation<TPixel> : IRowOperation
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
public int GetRequiredBufferLength(Rectangle bounds) => bounds.Width;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span)
{
Span<Vector4> vectorBuffer = span.Slice(0, this.bounds.Width);
ref Vector4 vectorRef = ref MemoryMarshal.GetReference(vectorBuffer);
ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan());
Span<TPixel> pixelRow = this.source.DangerousGetRowSpan(y);
int levels = this.luminanceLevels;
Span<TPixel> pixelRow = this.source.DangerousGetRowSpan(y);
PixelOperations<TPixel>.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));
}

5
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs

@ -93,6 +93,11 @@ internal class GlowProcessor<TPixel> : ImageProcessor<TPixel>
this.source = source;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<float> span)
{

5
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs

@ -101,6 +101,11 @@ internal class VignetteProcessor<TPixel> : ImageProcessor<TPixel>
this.source = source;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<float> span)
{

5
src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs

@ -176,6 +176,11 @@ internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel>, IR
this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Width, destination.Width);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span)
{

5
src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs

@ -176,6 +176,11 @@ internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel>
this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Width, destination.Width);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span)
{

6
tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs

@ -413,6 +413,9 @@ public class ParallelRowIteratorTests
public TestRowIntervalOperation(Action<RowInterval> 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<TBuffer> action)
=> this.action = action;
public int GetRequiredBufferLength(Rectangle bounds)
=> bounds.Width;
public void Invoke(in RowInterval rows, Span<TBuffer> span)
=> this.action(rows, span);
}

3
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<Vector4> span)
{
for (int y = rows.Min; y < rows.Max; y++)

Loading…
Cancel
Save