Browse Source

Fix arguments order and tests

af/octree-no-pixelmap
James Jackson-South 6 years ago
parent
commit
33bd5a920c
  1. 130
      src/ImageSharp/Advanced/ParallelRowIterator.cs
  2. 5
      src/ImageSharp/ImageFrame{TPixel}.cs
  3. 6
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs
  4. 20
      src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs
  5. 5
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs
  6. 10
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  7. 6
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
  8. 5
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  9. 5
      src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
  10. 5
      src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs
  11. 5
      src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs
  12. 5
      src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs
  13. 11
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs
  14. 10
      src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs
  15. 5
      src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs
  16. 5
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs
  17. 5
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs
  18. 10
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  19. 5
      src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs
  20. 10
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
  21. 5
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  22. 17
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs
  23. 277
      tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs
  24. 53
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

130
src/ImageSharp/Advanced/ParallelRowIterator.cs

@ -21,15 +21,15 @@ namespace SixLabors.ImageSharp.Advanced
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s. /// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary> /// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam> /// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param> /// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param> /// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static void IterateRows<T>(Rectangle rectangle, Configuration configuration, in T body) public static void IterateRows<T>(Configuration configuration, Rectangle rectangle, in T operation)
where T : struct, IRowIntervalOperation where T : struct, IRowIntervalOperation
{ {
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, in parallelSettings, in body); IterateRows(rectangle, in parallelSettings, in operation);
} }
/// <summary> /// <summary>
@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <typeparam name="T">The type of row operation to perform.</typeparam> /// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param> /// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param> /// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="operation">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param> /// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows<T>( public static void IterateRows<T>(
Rectangle rectangle, Rectangle rectangle,
in ParallelExecutionSettings parallelSettings, in ParallelExecutionSettings parallelSettings,
@ -81,10 +81,10 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary> /// </summary>
/// <typeparam name="T">The type of row operation to perform.</typeparam> /// <typeparam name="T">The type of row operation to perform.</typeparam>
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam> /// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param> /// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="operation">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param> /// <param name="rectangle">The <see cref="Rectangle"/>.</param>
public static void IterateRows<T, TBuffer>(Rectangle rectangle, Configuration configuration, in T operation) /// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows<T, TBuffer>(Configuration configuration, Rectangle rectangle, in T operation)
where T : struct, IRowIntervalOperation<TBuffer> where T : struct, IRowIntervalOperation<TBuffer>
where TBuffer : unmanaged where TBuffer : unmanaged
{ {
@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Advanced
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam> /// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param> /// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param> /// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="operation">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param> /// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
public static void IterateRows<T, TBuffer>( public static void IterateRows<T, TBuffer>(
Rectangle rectangle, Rectangle rectangle,
in ParallelExecutionSettings parallelSettings, in ParallelExecutionSettings parallelSettings,
@ -143,118 +143,6 @@ namespace SixLabors.ImageSharp.Advanced
rowOperation.Invoke); rowOperation.Invoke);
} }
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
internal static void IterateRows(Rectangle rectangle, Configuration configuration, Action<RowInterval> body)
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, in parallelSettings, body);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
/// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
/// <param name="body">The method body defining the iteration logic on a single <see cref="RowInterval"/>.</param>
internal static void IterateRows(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
Action<RowInterval> body)
{
ValidateRectangle(rectangle);
int top = rectangle.Top;
int bottom = rectangle.Bottom;
int width = rectangle.Width;
int height = rectangle.Height;
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
{
var rows = new RowInterval(top, bottom);
body(rows);
return;
}
int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep);
var rowAction = new WrappingRowIntervalOperation(in rowInfo, body);
Parallel.For(
0,
numOfSteps,
parallelOptions,
rowAction.Invoke);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="body"/> invocation.
/// </summary>
internal static void IterateRows<TBuffer>(
Rectangle rectangle,
Configuration configuration,
Action<RowInterval, Memory<TBuffer>> body)
where TBuffer : unmanaged
{
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
IterateRows(rectangle, in parallelSettings, body);
}
/// <summary>
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
/// instantiating a temporary buffer for each <paramref name="body"/> invocation.
/// </summary>
internal static void IterateRows<TBuffer>(
Rectangle rectangle,
in ParallelExecutionSettings parallelSettings,
Action<RowInterval, Memory<TBuffer>> body)
where TBuffer : unmanaged
{
ValidateRectangle(rectangle);
int top = rectangle.Top;
int bottom = rectangle.Bottom;
int width = rectangle.Width;
int height = rectangle.Height;
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
// Avoid TPL overhead in this trivial case:
if (numOfSteps == 1)
{
var rows = new RowInterval(top, bottom);
using (IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(width))
{
body(rows, buffer.Memory);
}
return;
}
int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
var rowInfo = new WrappingRowIntervalInfo(top, bottom, verticalStep, width);
var rowAction = new WrappingRowIntervalBufferOperation<TBuffer>(in rowInfo, allocator, body);
Parallel.For(
0,
numOfSteps,
parallelOptions,
rowAction.Invoke);
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);

5
src/ImageSharp/ImageFrame{TPixel}.cs

@ -259,11 +259,12 @@ namespace SixLabors.ImageSharp
} }
var target = new ImageFrame<TPixel2>(configuration, this.Width, this.Height, this.Metadata.DeepClone()); var target = new ImageFrame<TPixel2>(configuration, this.Width, this.Height, this.Metadata.DeepClone());
var operation = new RowIntervalOperation<TPixel2>(this, target, configuration);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
this.Bounds(),
configuration, configuration,
new RowIntervalOperation<TPixel2>(this, target, configuration)); this.Bounds(),
in operation);
return target; return target;
} }

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

@ -50,11 +50,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization
bool isAlphaOnly = typeof(TPixel) == typeof(A8); bool isAlphaOnly = typeof(TPixel) == typeof(A8);
var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY);
var operation = new RowIntervalOperation(source, upper, lower, threshold, startX, endX, isAlphaOnly);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
workingRect,
configuration, configuration,
new RowIntervalOperation(source, upper, lower, threshold, startX, endX, isAlphaOnly)); workingRect,
in operation);
} }
/// <summary> /// <summary>

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

@ -268,10 +268,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
// Preliminary gamma highlight pass // Preliminary gamma highlight pass
var gammaOperation = new ApplyGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma);
ParallelRowIterator.IterateRows<ApplyGammaExposureRowIntervalOperation, Vector4>( ParallelRowIterator.IterateRows<ApplyGammaExposureRowIntervalOperation, Vector4>(
this.SourceRectangle,
this.Configuration, this.Configuration,
new ApplyGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); this.SourceRectangle,
in gammaOperation);
// Create a 0-filled buffer to use to store the result of the component convolutions // Create a 0-filled buffer to use to store the result of the component convolutions
using Buffer2D<Vector4> processingBuffer = this.Configuration.MemoryAllocator.Allocate2D<Vector4>(source.Size(), AllocationOptions.Clean); using Buffer2D<Vector4> processingBuffer = this.Configuration.MemoryAllocator.Allocate2D<Vector4>(source.Size(), AllocationOptions.Clean);
@ -282,10 +283,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
float inverseGamma = 1 / this.gamma; float inverseGamma = 1 / this.gamma;
// Apply the inverse gamma exposure pass, and write the final pixel data // Apply the inverse gamma exposure pass, and write the final pixel data
var operation = new ApplyInverseGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
this.SourceRectangle,
this.Configuration, this.Configuration,
new ApplyInverseGammaExposureRowIntervalOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); this.SourceRectangle,
in operation);
} }
/// <summary> /// <summary>
@ -314,16 +316,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Vector4 parameters = Unsafe.Add(ref paramsRef, i); Vector4 parameters = Unsafe.Add(ref paramsRef, i);
// Compute the vertical 1D convolution // Compute the vertical 1D convolution
var verticalOperation = new ApplyVerticalConvolutionRowIntervalOperation(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
sourceRectangle,
configuration, configuration,
new ApplyVerticalConvolutionRowIntervalOperation(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); sourceRectangle,
in verticalOperation);
// Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer
var horizontalOperation = new ApplyHorizontalConvolutionRowIntervalOperation(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
sourceRectangle,
configuration, configuration,
new ApplyHorizontalConvolutionRowIntervalOperation(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); sourceRectangle,
in horizontalOperation);
} }
} }

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

@ -65,11 +65,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
source.CopyTo(targetPixels); source.CopyTo(targetPixels);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>( ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
interest,
this.Configuration, this.Configuration,
new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha)); interest,
in operation);
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels); Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
} }

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

@ -64,16 +64,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
// Horizontal convolution // Horizontal convolution
var horizontalOperation = new RowIntervalOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>( ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
interest,
this.Configuration, this.Configuration,
new RowIntervalOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); interest,
in horizontalOperation);
// Vertical convolution // Vertical convolution
var verticalOperation = new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>( ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
interest,
this.Configuration, this.Configuration,
new RowIntervalOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); interest,
in verticalOperation);
} }
/// <summary> /// <summary>

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

@ -56,11 +56,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
source.CopyTo(targetPixels); source.CopyTo(targetPixels);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
var operation = new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>( ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
interest,
this.Configuration, this.Configuration,
new RowIntervalOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha)); interest,
in operation);
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels); Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
} }

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

@ -102,10 +102,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
processor.Apply(pass); processor.Apply(pass);
} }
var operation = new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
Rectangle.FromLTRB(minX, minY, maxX, maxY),
this.Configuration, this.Configuration,
new RowIntervalOperation(source.PixelBuffer, pass.PixelBuffer, minX, maxX, shiftY, shiftX)); Rectangle.FromLTRB(minX, minY, maxX, maxY),
in operation);
} }
} }

5
src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs

@ -99,10 +99,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
"Cannot draw image because the source image does not overlap the target image."); "Cannot draw image because the source image does not overlap the target image.");
} }
var operation = new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
workingRect,
configuration, configuration,
new RowIntervalOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity)); workingRect,
in operation);
} }
/// <summary> /// <summary>

5
src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs

@ -47,10 +47,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
source.CopyTo(targetPixels); source.CopyTo(targetPixels);
var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
this.SourceRectangle,
this.Configuration, this.Configuration,
new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels)); this.SourceRectangle,
in operation);
Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels); Buffer2D<TPixel>.SwapOrCopyContent(source.PixelBuffer, targetPixels);
} }

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

@ -50,11 +50,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
var operation = new RowIntervalOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>( ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
interest,
this.Configuration, this.Configuration,
new RowIntervalOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate)); interest,
in operation);
} }
/// <summary> /// <summary>

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

@ -36,11 +36,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
var operation = new RowIntervalOperation(interest.X, source, this.definition.Matrix, this.Configuration);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>( ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
interest,
this.Configuration, this.Configuration,
new RowIntervalOperation(interest.X, source, this.definition.Matrix, this.Configuration)); interest,
in operation);
} }
/// <summary> /// <summary>

11
src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs

@ -80,10 +80,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
yStart += tileHeight; yStart += tileHeight;
} }
var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count),
this.Configuration, this.Configuration,
new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source)); new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count),
in operation);
ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); ref TPixel pixelsBase = ref source.GetPixelReference(0, 0);
@ -510,7 +511,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public void CalculateLookupTables(ImageFrame<TPixel> source, HistogramEqualizationProcessor<TPixel> processor) public void CalculateLookupTables(ImageFrame<TPixel> source, HistogramEqualizationProcessor<TPixel> processor)
{ {
var rowOperation = new RowIntervalOperation( var operation = new RowIntervalOperation(
processor, processor,
this.memoryAllocator, this.memoryAllocator,
this.cdfMinBuffer2D, this.cdfMinBuffer2D,
@ -522,9 +523,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
source); source);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count),
this.configuration, this.configuration,
in rowOperation); new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count),
in operation);
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]

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

@ -52,10 +52,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
using IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean); using IMemoryOwner<int> histogramBuffer = memoryAllocator.Allocate<int>(this.LuminanceLevels, AllocationOptions.Clean);
// Build the histogram of the grayscale levels // Build the histogram of the grayscale levels
var grayscaleOperation = new GrayscaleLevelsRowIntervalOperation(workingRect, histogramBuffer, source, this.LuminanceLevels);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
workingRect,
this.Configuration, this.Configuration,
new GrayscaleLevelsRowIntervalOperation(workingRect, histogramBuffer, source, this.LuminanceLevels)); workingRect,
in grayscaleOperation);
Span<int> histogram = histogramBuffer.GetSpan(); Span<int> histogram = histogramBuffer.GetSpan();
if (this.ClipHistogramEnabled) if (this.ClipHistogramEnabled)
@ -74,10 +75,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin;
// Apply the cdf to each pixel of the image // Apply the cdf to each pixel of the image
var cdfOperation = new CdfApplicationRowIntervalOperation(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
workingRect,
this.Configuration, this.Configuration,
new CdfApplicationRowIntervalOperation(workingRect, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin)); workingRect,
in cdfOperation);
} }
/// <summary> /// <summary>

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

@ -49,10 +49,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(graphicsOptions); PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(graphicsOptions);
var operation = new RowIntervalOperation(configuration, interest, blender, amount, colors, source);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
interest,
configuration, configuration,
new RowIntervalOperation(configuration, interest, blender, amount, colors, source)); interest,
in operation);
} }
private readonly struct RowIntervalOperation : IRowIntervalOperation private readonly struct RowIntervalOperation : IRowIntervalOperation

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

@ -55,10 +55,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
using IMemoryOwner<TPixel> rowColors = allocator.Allocate<TPixel>(interest.Width); using IMemoryOwner<TPixel> rowColors = allocator.Allocate<TPixel>(interest.Width);
rowColors.GetSpan().Fill(glowColor); rowColors.GetSpan().Fill(glowColor);
var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source);
ParallelRowIterator.IterateRows<RowIntervalOperation, float>( ParallelRowIterator.IterateRows<RowIntervalOperation, float>(
interest,
configuration, configuration,
new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); interest,
in operation);
} }
private readonly struct RowIntervalOperation : IRowIntervalOperation<float> private readonly struct RowIntervalOperation : IRowIntervalOperation<float>

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

@ -63,10 +63,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
using IMemoryOwner<TPixel> rowColors = allocator.Allocate<TPixel>(interest.Width); using IMemoryOwner<TPixel> rowColors = allocator.Allocate<TPixel>(interest.Width);
rowColors.GetSpan().Fill(vignetteColor); rowColors.GetSpan().Fill(vignetteColor);
var operation = new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source);
ParallelRowIterator.IterateRows<RowIntervalOperation, float>( ParallelRowIterator.IterateRows<RowIntervalOperation, float>(
interest,
configuration, configuration,
new RowIntervalOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source)); interest,
in operation);
} }
private readonly struct RowIntervalOperation : IRowIntervalOperation<float> private readonly struct RowIntervalOperation : IRowIntervalOperation<float>

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

@ -58,20 +58,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (this.resampler is NearestNeighborResampler) if (this.resampler is NearestNeighborResampler)
{ {
var nnOperation = new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
targetBounds,
configuration, configuration,
new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination)); targetBounds,
in nnOperation);
return; return;
} }
using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>( ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
targetBounds,
configuration, configuration,
new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination)); targetBounds,
in operation);
} }
/// <summary> /// <summary>

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

@ -76,10 +76,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
private void FlipY(ImageFrame<TPixel> source, Configuration configuration) private void FlipY(ImageFrame<TPixel> source, Configuration configuration)
{ {
var operation = new RowIntervalOperation(source);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
source.Bounds(),
configuration, configuration,
new RowIntervalOperation(source)); source.Bounds(),
in operation);
} }
private readonly struct RowIntervalOperation : IRowIntervalOperation private readonly struct RowIntervalOperation : IRowIntervalOperation

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

@ -60,20 +60,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
Rectangle sourceBounds = this.SourceRectangle; Rectangle sourceBounds = this.SourceRectangle;
var nnOperation = new NearestNeighborRowIntervalOperation(ref sourceBounds, ref matrix, width, source, destination);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
targetBounds,
configuration, configuration,
new NearestNeighborRowIntervalOperation(ref sourceBounds, ref matrix, width, source, destination)); targetBounds,
in nnOperation);
return; return;
} }
using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>( ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
targetBounds,
configuration, configuration,
new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination)); targetBounds,
in operation);
} }
private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation

5
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -95,10 +95,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height;
var operation = new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
interest,
configuration, configuration,
new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination)); interest,
in operation);
return; return;
} }

17
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class RotateProcessor<TPixel> : AffineTransformProcessor<TPixel> internal class RotateProcessor<TPixel> : AffineTransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private float degrees; private readonly float degrees;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class.
@ -131,10 +131,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
private void Rotate180(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) private void Rotate180(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration)
{ {
var operation = new Rotate180RowIntervalOperation(source.Width, source.Height, source, destination);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
source.Bounds(),
configuration, configuration,
new Rotate180RowIntervalOperation(source.Width, source.Height, source, destination)); source.Bounds(),
in operation);
} }
/// <summary> /// <summary>
@ -145,10 +146,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
private void Rotate270(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) private void Rotate270(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration)
{ {
var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
source.Bounds(),
configuration, configuration,
new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination)); source.Bounds(),
in operation);
} }
/// <summary> /// <summary>
@ -159,10 +161,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
private void Rotate90(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) private void Rotate90(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration)
{ {
var operation = new Rotate90RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
source.Bounds(),
configuration, configuration,
new Rotate90RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination)); source.Bounds(),
in operation);
} }
private readonly struct Rotate180RowIntervalOperation : IRowIntervalOperation private readonly struct Rotate180RowIntervalOperation : IRowIntervalOperation

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

@ -10,7 +10,6 @@ using System.Threading;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -30,20 +29,20 @@ namespace SixLabors.ImageSharp.Tests.Helpers
/// </summary> /// </summary>
public static TheoryData<int, int, int, int, int, int> IterateRows_OverMinimumPixelsLimit_Data = public static TheoryData<int, int, int, int, int, int> IterateRows_OverMinimumPixelsLimit_Data =
new TheoryData<int, int, int, int, int, int> new TheoryData<int, int, int, int, int, int>
{ {
{ 1, 0, 100, -1, 100, 1 }, { 1, 0, 100, -1, 100, 1 },
{ 2, 0, 9, 5, 4, 2 }, { 2, 0, 9, 5, 4, 2 },
{ 4, 0, 19, 5, 4, 4 }, { 4, 0, 19, 5, 4, 4 },
{ 2, 10, 19, 5, 4, 2 }, { 2, 10, 19, 5, 4, 2 },
{ 4, 0, 200, 50, 50, 4 }, { 4, 0, 200, 50, 50, 4 },
{ 4, 123, 323, 50, 50, 4 }, { 4, 123, 323, 50, 50, 4 },
{ 4, 0, 1201, 301, 298, 4 }, { 4, 0, 1201, 301, 298, 4 },
{ 8, 10, 236, 29, 23, 8 }, { 8, 10, 236, 29, 23, 8 },
{ 16, 0, 209, 14, 13, 15 }, { 16, 0, 209, 14, 13, 15 },
{ 24, 0, 209, 9, 2, 24 }, { 24, 0, 209, 9, 2, 24 },
{ 32, 0, 209, 7, 6, 30 }, { 32, 0, 209, 7, 6, 30 },
{ 64, 0, 209, 4, 1, 53 }, { 64, 0, 209, 4, 1, 53 },
}; };
[Theory] [Theory]
[MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))]
@ -64,20 +63,24 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int actualNumberOfSteps = 0; int actualNumberOfSteps = 0;
ParallelRowIterator.IterateRows( void RowAction(RowInterval rows)
rectangle, {
parallelSettings, Assert.True(rows.Min >= minY);
rows => Assert.True(rows.Max <= maxY);
{
Assert.True(rows.Min >= minY); int step = rows.Max - rows.Min;
Assert.True(rows.Max <= maxY); int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength;
int step = rows.Max - rows.Min; Interlocked.Increment(ref actualNumberOfSteps);
int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; Assert.Equal(expected, step);
}
Interlocked.Increment(ref actualNumberOfSteps); var operation = new TestRowIntervalOperation(RowAction);
Assert.Equal(expected, step);
}); ParallelRowIterator.IterateRows(
rectangle,
in parallelSettings,
in operation);
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
} }
@ -102,16 +105,20 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray();
var actualData = new int[maxY]; var actualData = new int[maxY];
void RowAction(RowInterval rows)
{
for (int y = rows.Min; y < rows.Max; y++)
{
actualData[y] = y;
}
}
var operation = new TestRowIntervalOperation(RowAction);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
rectangle, rectangle,
parallelSettings, in parallelSettings,
rows => in operation);
{
for (int y = rows.Min; y < rows.Max; y++)
{
actualData[y] = y;
}
});
Assert.Equal(expectedData, actualData); Assert.Equal(expectedData, actualData);
} }
@ -136,22 +143,27 @@ namespace SixLabors.ImageSharp.Tests.Helpers
var bufferHashes = new ConcurrentBag<int>(); var bufferHashes = new ConcurrentBag<int>();
int actualNumberOfSteps = 0; int actualNumberOfSteps = 0;
ParallelRowIterator.IterateRows(
rectangle,
parallelSettings,
(RowInterval rows, Memory<Vector4> buffer) =>
{
Assert.True(rows.Min >= minY);
Assert.True(rows.Max <= maxY);
bufferHashes.Add(buffer.GetHashCode()); void RowAction(RowInterval rows, Memory<Vector4> buffer)
{
Assert.True(rows.Min >= minY);
Assert.True(rows.Max <= maxY);
bufferHashes.Add(buffer.GetHashCode());
int step = rows.Max - rows.Min;
int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength;
Interlocked.Increment(ref actualNumberOfSteps);
Assert.Equal(expected, step);
}
int step = rows.Max - rows.Min; var operation = new TestRowIntervalOperation<Vector4>(RowAction);
int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength;
Interlocked.Increment(ref actualNumberOfSteps); ParallelRowIterator.IterateRows<TestRowIntervalOperation<Vector4>, Vector4>(
Assert.Equal(expected, step); rectangle,
}); in parallelSettings,
in operation);
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
@ -179,31 +191,35 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray();
var actualData = new int[maxY]; var actualData = new int[maxY];
ParallelRowIterator.IterateRows( void RowAction(RowInterval rows, Memory<Vector4> buffer)
{
for (int y = rows.Min; y < rows.Max; y++)
{
actualData[y] = y;
}
}
var operation = new TestRowIntervalOperation<Vector4>(RowAction);
ParallelRowIterator.IterateRows<TestRowIntervalOperation<Vector4>, Vector4>(
rectangle, rectangle,
parallelSettings, in parallelSettings,
(RowInterval rows, Memory<Vector4> buffer) => in operation);
{
for (int y = rows.Min; y < rows.Max; y++)
{
actualData[y] = y;
}
});
Assert.Equal(expectedData, actualData); Assert.Equal(expectedData, actualData);
} }
public static TheoryData<int, int, int, int, int, int, int> IterateRows_WithEffectiveMinimumPixelsLimit_Data = public static TheoryData<int, int, int, int, int, int, int> IterateRows_WithEffectiveMinimumPixelsLimit_Data =
new TheoryData<int, int, int, int, int, int, int> new TheoryData<int, int, int, int, int, int, int>
{ {
{ 2, 200, 50, 2, 1, -1, 2 }, { 2, 200, 50, 2, 1, -1, 2 },
{ 2, 200, 200, 1, 1, -1, 1 }, { 2, 200, 200, 1, 1, -1, 1 },
{ 4, 200, 100, 4, 2, 2, 2 }, { 4, 200, 100, 4, 2, 2, 2 },
{ 4, 300, 100, 8, 3, 3, 2 }, { 4, 300, 100, 8, 3, 3, 2 },
{ 2, 5000, 1, 4500, 1, -1, 4500 }, { 2, 5000, 1, 4500, 1, -1, 4500 },
{ 2, 5000, 1, 5000, 1, -1, 5000 }, { 2, 5000, 1, 5000, 1, -1, 5000 },
{ 2, 5000, 1, 5001, 2, 2501, 2500 }, { 2, 5000, 1, 5001, 2, 2501, 2500 },
}; };
[Theory] [Theory]
[MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))]
@ -225,20 +241,24 @@ namespace SixLabors.ImageSharp.Tests.Helpers
int actualNumberOfSteps = 0; int actualNumberOfSteps = 0;
ParallelRowIterator.IterateRows( void RowAction(RowInterval rows)
rectangle, {
parallelSettings, Assert.True(rows.Min >= 0);
rows => Assert.True(rows.Max <= height);
{
Assert.True(rows.Min >= 0); int step = rows.Max - rows.Min;
Assert.True(rows.Max <= height); int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength;
Interlocked.Increment(ref actualNumberOfSteps);
Assert.Equal(expected, step);
}
int step = rows.Max - rows.Min; var operation = new TestRowIntervalOperation(RowAction);
int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength;
Interlocked.Increment(ref actualNumberOfSteps); ParallelRowIterator.IterateRows(
Assert.Equal(expected, step); rectangle,
}); in parallelSettings,
in operation);
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
} }
@ -262,33 +282,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers
var rectangle = new Rectangle(0, 0, width, height); var rectangle = new Rectangle(0, 0, width, height);
int actualNumberOfSteps = 0; int actualNumberOfSteps = 0;
ParallelRowIterator.IterateRows(
rectangle,
parallelSettings,
(RowInterval rows, Memory<Vector4> buffer) =>
{
Assert.True(rows.Min >= 0);
Assert.True(rows.Max <= height);
int step = rows.Max - rows.Min; void RowAction(RowInterval rows, Memory<Vector4> buffer)
int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; {
Assert.True(rows.Min >= 0);
Assert.True(rows.Max <= height);
Interlocked.Increment(ref actualNumberOfSteps); int step = rows.Max - rows.Min;
Assert.Equal(expected, step); int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength;
});
Interlocked.Increment(ref actualNumberOfSteps);
Assert.Equal(expected, step);
}
var operation = new TestRowIntervalOperation<Vector4>(RowAction);
ParallelRowIterator.IterateRows<TestRowIntervalOperation<Vector4>, Vector4>(
rectangle,
in parallelSettings,
in operation);
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
} }
public static readonly TheoryData<int, int, int, int, int, int, int> IterateRectangularBuffer_Data = public static readonly TheoryData<int, int, int, int, int, int, int> IterateRectangularBuffer_Data =
new TheoryData<int, int, int, int, int, int, int> new TheoryData<int, int, int, int, int, int, int>
{ {
{ 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox
{ 2, 582, 453, 10, 10, 291, 226 }, { 2, 582, 453, 10, 10, 291, 226 },
{ 16, 582, 453, 10, 10, 291, 226 }, { 16, 582, 453, 10, 10, 291, 226 },
{ 16, 582, 453, 10, 10, 1, 226 }, { 16, 582, 453, 10, 10, 1, 226 },
{ 16, 1, 453, 0, 10, 1, 226 }, { 16, 1, 453, 0, 10, 1, 226 },
}; };
[Theory] [Theory]
[MemberData(nameof(IterateRectangularBuffer_Data))] [MemberData(nameof(IterateRectangularBuffer_Data))]
@ -325,17 +350,21 @@ namespace SixLabors.ImageSharp.Tests.Helpers
// Fill actual data using IterateRows: // Fill actual data using IterateRows:
var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator);
void RowAction(RowInterval rows)
{
this.output.WriteLine(rows.ToString());
for (int y = rows.Min; y < rows.Max; y++)
{
FillRow(y, actual);
}
}
var operation = new TestRowIntervalOperation(RowAction);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
rect, rect,
settings, settings,
rows => in operation);
{
this.output.WriteLine(rows.ToString());
for (int y = rows.Min; y < rows.Max; y++)
{
FillRow(y, actual);
}
});
// Assert: // Assert:
TestImageExtensions.CompareBuffers(expected.GetSpan(), actual.GetSpan()); TestImageExtensions.CompareBuffers(expected.GetSpan(), actual.GetSpan());
@ -353,8 +382,14 @@ namespace SixLabors.ImageSharp.Tests.Helpers
var rect = new Rectangle(0, 0, width, height); var rect = new Rectangle(0, 0, width, height);
void RowAction(RowInterval rows)
{
}
var operation = new TestRowIntervalOperation(RowAction);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>( ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
() => ParallelRowIterator.IterateRows(rect, parallelSettings, rows => { })); () => ParallelRowIterator.IterateRows(rect, in parallelSettings, in operation));
Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message);
} }
@ -370,10 +405,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers
var rect = new Rectangle(0, 0, width, height); var rect = new Rectangle(0, 0, width, height);
void RowAction(RowInterval rows, Memory<Rgba32> memory)
{
}
var operation = new TestRowIntervalOperation<Rgba32>(RowAction);
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>( ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(
() => ParallelRowIterator.IterateRows<Rgba32>(rect, parallelSettings, (rows, memory) => { })); () => ParallelRowIterator.IterateRows<TestRowIntervalOperation<Rgba32>, Rgba32>(rect, in parallelSettings, in operation));
Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message);
} }
private readonly struct TestRowIntervalOperation : IRowIntervalOperation
{
private readonly Action<RowInterval> action;
public TestRowIntervalOperation(Action<RowInterval> action)
=> this.action = action;
public void Invoke(in RowInterval rows) => this.action(rows);
}
private readonly struct TestRowIntervalOperation<TBuffer> : IRowIntervalOperation<TBuffer>
where TBuffer : unmanaged
{
private readonly Action<RowInterval, Memory<TBuffer>> action;
public TestRowIntervalOperation(Action<RowInterval, Memory<TBuffer>> action)
=> this.action = action;
public void Invoke(in RowInterval rows, Memory<TBuffer> memory)
=> this.action(rows, memory);
}
} }
} }

53
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -702,25 +702,44 @@ namespace SixLabors.ImageSharp.Tests
{ {
Rectangle sourceRectangle = this.SourceRectangle; Rectangle sourceRectangle = this.SourceRectangle;
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
ParallelRowIterator.IterateRows<Vector4>(
sourceRectangle, var operation = new RowOperation(configuration, sourceRectangle, source);
ParallelRowIterator.IterateRows<RowOperation, Vector4>(
configuration, configuration,
(rows, temp) => sourceRectangle,
in operation);
}
private readonly struct RowOperation : IRowIntervalOperation<Vector4>
{
private readonly Configuration configuration;
private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source;
public RowOperation(Configuration configuration, Rectangle bounds, ImageFrame<TPixel> source)
{
this.configuration = configuration;
this.bounds = bounds;
this.source = source;
}
public void Invoke(in RowInterval rows, Memory<Vector4> memory)
{
Span<Vector4> tempSpan = memory.Span;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> rowSpan = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left, this.bounds.Width);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale);
for (int i = 0; i < tempSpan.Length; i++)
{ {
Span<Vector4> tempSpan = temp.Span; ref Vector4 v = ref tempSpan[i];
for (int y = rows.Min; y < rows.Max; y++) v.W = 1F;
{ }
Span<TPixel> rowSpan = source.GetPixelRowSpan(y).Slice(sourceRectangle.Left, sourceRectangle.Width);
PixelOperations<TPixel>.Instance.ToVector4(configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale); PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale);
for (int i = 0; i < tempSpan.Length; i++) }
{ }
ref Vector4 v = ref tempSpan[i];
v.W = 1F;
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale);
}
});
} }
} }
} }

Loading…
Cancel
Save