diff --git a/src/ImageSharp/Common/Helpers/ParallelFor.cs b/src/ImageSharp/Common/Helpers/ParallelFor.cs
index 4c14bb6e3..7aa50a9e3 100644
--- a/src/ImageSharp/Common/Helpers/ParallelFor.cs
+++ b/src/ImageSharp/Common/Helpers/ParallelFor.cs
@@ -1,10 +1,63 @@
using System;
using System.Buffers;
+using System.Runtime.CompilerServices;
using System.Threading.Tasks;
+
+using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
+using SixLabors.Primitives;
namespace SixLabors.ImageSharp
{
+ internal readonly struct RowInterval
+ {
+ public RowInterval(int min, int max)
+ {
+ this.Min = min;
+ this.Max = max;
+ }
+
+ ///
+ /// Gets the INCLUSIVE minimum
+ ///
+ public int Min { get; }
+
+ ///
+ /// Gets the EXCLUSIVE maximum
+ ///
+ public int Max { get; }
+ }
+
+ internal static class ParallelHelper
+ {
+ public static void IterateRows(in Rectangle rectangle, Configuration configuration, Action body)
+ {
+ int maxSteps = (int)Math.Ceiling(
+ (float)(rectangle.Width * rectangle.Height) / configuration.MinimumPixelsProcessedPerTask);
+
+ int numOfSteps = Math.Min(configuration.MaxDegreeOfParallelism, maxSteps);
+
+ int step = rectangle.Height / numOfSteps;
+
+ var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps };
+
+ int bottom = rectangle.Bottom;
+
+ Parallel.For(
+ 0,
+ numOfSteps,
+ parallelOptions,
+ i =>
+ {
+ int yMin = i * step;
+ int yMax = Math.Min(yMin + step, bottom);
+
+ var rowInterval = new RowInterval(yMin, yMax);
+ body(rowInterval);
+ });
+ }
+ }
+
///
/// Utility methods for Parallel.For() execution. Use this instead of raw calls!
///
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 1b009bfed..4bd6bd9e5 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -29,6 +29,8 @@ namespace SixLabors.ImageSharp
private int maxDegreeOfParallelism = Environment.ProcessorCount;
+ private int minimumPixelsPerTask = 2048;
+
///
/// Initializes a new instance of the class.
///
@@ -59,6 +61,7 @@ namespace SixLabors.ImageSharp
///
/// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms
/// configured with this instance.
+ /// Initialized with by default.
///
public int MaxDegreeOfParallelism
{
@@ -74,6 +77,24 @@ namespace SixLabors.ImageSharp
}
}
+ ///
+ /// Gets or sets the minimum number of pixels being processed by a single task when parallelizing operations with TPL.
+ /// It's not worth to launch tasks processing pixels below this limit. Initialized with 2048 by default.
+ ///
+ public int MinimumPixelsProcessedPerTask
+ {
+ get => this.minimumPixelsPerTask;
+ set
+ {
+ if (value <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(this.MinimumPixelsProcessedPerTask));
+ }
+
+ this.minimumPixelsPerTask = value;
+ }
+ }
+
///
/// Gets the currently registered s.
///
diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs
index 107457ae7..a9edb9cfb 100644
--- a/src/ImageSharp/Memory/Buffer2DExtensions.cs
+++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs
@@ -96,15 +96,14 @@ namespace SixLabors.ImageSharp.Memory
/// The
/// The rectangle subarea
/// The
- public static BufferArea GetArea(this Buffer2D buffer, Rectangle rectangle)
+ public static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle)
where T : struct => new BufferArea(buffer, rectangle);
public static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height)
- where T : struct
- {
- var rectangle = new Rectangle(x, y, width, height);
- return new BufferArea(buffer, rectangle);
- }
+ where T : struct => new BufferArea(buffer, new Rectangle(x, y, width, height));
+
+ public static BufferArea GetAreaBetweenRows(this Buffer2D buffer, int minY, int maxY)
+ where T : struct => new BufferArea(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY));
///
/// Return a to the whole area of 'buffer'
diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs
new file mode 100644
index 000000000..ca904f953
--- /dev/null
+++ b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Threading;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.Primitives;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Helpers
+{
+ public class ParallelHelperTests
+ {
+ [Theory]
+ [InlineData(1, 0, 100, -1, 100)]
+ [InlineData(2, 0, 9, 5, 4)]
+ [InlineData(2, 10, 19, 5, 4)]
+ [InlineData(4, 0, 200, 50, 50)]
+ [InlineData(4, 123, 323, 50, 50)]
+ public void IterateRows_OverMinimumPixelsLimit(
+ int maxDegreeOfParallelism,
+ int minY,
+ int maxY,
+ int expectedStep,
+ int expectedLastStep)
+ {
+ Configuration cfg = Configuration.Default.ShallowCopy();
+ cfg.MinimumPixelsProcessedPerTask = 1;
+ cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism;
+
+ var rectangle = new Rectangle(0, minY, 10, maxY);
+
+ int actualNumberOfSteps = 0;
+ ParallelHelper.IterateRows(rectangle, cfg,
+ rows =>
+ {
+ Assert.True(rows.Min >= minY);
+ int step = rows.Max - rows.Min;
+ int expected = rows.Max < maxY ? expectedStep : expectedLastStep;
+
+ Interlocked.Increment(ref actualNumberOfSteps);
+ Assert.Equal(expected, step);
+ });
+
+ Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps);
+ }
+
+ [Theory]
+ [InlineData(2, 200, 50, 2, 1, -1, 2)]
+ [InlineData(2, 200, 200, 1, 1, -1, 1)]
+ [InlineData(4, 200, 100, 4, 2, 1, 1)]
+ [InlineData(4, 300, 100, 8, 3, 3, 2)]
+ public void IterateRows_WithEffectiveMinimumPixelsLimit(
+ int maxDegreeOfParallelism,
+ int minimumPixelsProcessedPerTask,
+ int width,
+ int height,
+ int expectedNumberOfSteps,
+ int expectedStep,
+ int expectedLastStep)
+ {
+ Configuration cfg = Configuration.Default.ShallowCopy();
+ cfg.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask;
+ cfg.MaxDegreeOfParallelism = maxDegreeOfParallelism;
+
+ var rectangle = new Rectangle(0, 0, width, height);
+
+ int actualNumberOfSteps = 0;
+ ParallelHelper.IterateRows(rectangle, cfg,
+ rows =>
+ {
+ int step = rows.Max - rows.Min;
+ int expected = rows.Max < height ? expectedStep : expectedLastStep;
+
+ Interlocked.Increment(ref actualNumberOfSteps);
+ Assert.Equal(expected, step);
+ });
+
+ Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps);
+ }
+ }
+}
\ No newline at end of file