diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs index a757f50c8..fbbc57946 100644 --- a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs +++ b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs @@ -44,6 +44,8 @@ namespace SixLabors.ImageSharp.ParallelUtils in ParallelExecutionSettings parallelSettings, Action body) { + DebugGuard.MustBeGreaterThan(rectangle.Width, 0, nameof(rectangle)); + int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); @@ -56,7 +58,7 @@ namespace SixLabors.ImageSharp.ParallelUtils return; } - int verticalStep = DivideRound(rectangle.Height, numOfSteps); + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps }; @@ -102,7 +104,7 @@ namespace SixLabors.ImageSharp.ParallelUtils return; } - int verticalStep = DivideRound(rectangle.Height, numOfSteps); + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps }; diff --git a/src/ImageSharp/Memory/RowInterval.cs b/src/ImageSharp/Memory/RowInterval.cs index 87b08251b..273a6aa34 100644 --- a/src/ImageSharp/Memory/RowInterval.cs +++ b/src/ImageSharp/Memory/RowInterval.cs @@ -32,5 +32,11 @@ namespace SixLabors.ImageSharp.Memory /// Gets the difference ( - ) /// public int Height => this.Max - this.Min; + + /// + public override string ToString() + { + return $"RowInterval [{this.Min}->{this.Max}["; + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs index c84354329..ef6b133f7 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs @@ -13,11 +13,19 @@ using SixLabors.Memory; using SixLabors.Primitives; using Xunit; +using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Helpers { public class ParallelHelperTests { + private readonly ITestOutputHelper Output; + + public ParallelHelperTests(ITestOutputHelper output) + { + this.Output = output; + } + /// /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength /// @@ -30,12 +38,13 @@ namespace SixLabors.ImageSharp.Tests.Helpers { 2, 10, 19, 5, 4 }, { 4, 0, 200, 50, 50 }, { 4, 123, 323, 50, 50 }, - { 4, 0, 1201, 300, 301 }, + { 4, 0, 1201, 301, 298 }, + { 8, 10, 236, 29, 23 } }; [Theory] [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] - public void IterateRows_OverMinimumPixelsLimit( + public void IterateRows_OverMinimumPixelsLimit_IntervalsAreCorrect( int maxDegreeOfParallelism, int minY, int maxY, @@ -50,6 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, minY, 10, maxY - minY); int actualNumberOfSteps = 0; + ParallelHelper.IterateRows( rectangle, parallelSettings, @@ -68,6 +78,40 @@ namespace SixLabors.ImageSharp.Tests.Helpers Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); } + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRows_OverMinimumPixelsLimit_ShouldVisitAllRows( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + + int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + int[] actualData = new int[maxY]; + + ParallelHelper.IterateRows( + rectangle, + parallelSettings, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + }); + + Assert.Equal(expectedData, actualData); + } + [Theory] [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( @@ -110,6 +154,40 @@ namespace SixLabors.ImageSharp.Tests.Helpers Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers); } + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + int[] actualData = new int[maxY]; + + ParallelHelper.IterateRowsWithTempBuffer( + rectangle, + parallelSettings, + (RowInterval rows, Memory buffer) => + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + }); + + Assert.Equal(expectedData, actualData); + + } + public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = new TheoryData() { @@ -200,8 +278,11 @@ namespace SixLabors.ImageSharp.Tests.Helpers public static readonly TheoryData IterateRectangularBuffer_Data = new TheoryData() { - { 8, 582, 453, 10, 10, 291, 226 }, // bounds in DetectEdgesTest.DetectEdges_InBox + { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox { 2, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 1, 226 }, + { 16, 1, 453, 0, 10, 1, 226 }, }; [Theory] @@ -217,33 +298,39 @@ namespace SixLabors.ImageSharp.Tests.Helpers { MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; - using (Buffer2D expected = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) - using (Buffer2D actual = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) + using (Buffer2D expected = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) + using (Buffer2D actual = memoryAllocator.Allocate2D(bufferWidth, bufferHeight, AllocationOptions.Clean)) { var rect = new Rectangle(rectX, rectY, rectWidth, rectHeight); - - for (int y = rectY; y < rect.Bottom; y++) + + void FillRow(int y, Buffer2D buffer) { for (int x = rect.Left; x < rect.Right; x++) { - expected[x, y] = y * 10000 + x; + buffer[x, y] = new Point(x, y); } } + // Fill Expected data: + for (int y = rectY; y < rect.Bottom; y++) + { + FillRow(y, expected); + } + + // Fill actual data using IterateRows: var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); ParallelHelper.IterateRows(rect, settings, rows => { + this.Output.WriteLine(rows.ToString()); for (int y = rows.Min; y < rows.Max; y++) { - for (int x = rect.Left; x < rect.Right; x++) - { - actual[x, y] = y * 10000 + x; - } + FillRow(y, actual); } }); + // Assert: TestImageExtensions.CompareBuffers(expected.Span, actual.Span); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 8b4b93344..2384333bf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -453,7 +453,10 @@ namespace SixLabors.ImageSharp.Tests for (int i = 0; i < expected.Length; i++) { - Assert.True(expected[i].Equals(actual[i]), $"Buffers differ at position {i}!"); + T x = expected[i]; + T a = actual[i]; + + Assert.True(x.Equals(a), $"Buffers differ at position {i}! Expected: {x} | Actual: {a}"); } }