From 7d415fbc27ca9815ffd6d819ed02f994bd2aba57 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 22 Sep 2018 23:45:46 +0200 Subject: [PATCH] ParallelHelper -> ProjectiveTransformProcessor, better RotateTests --- .../ProjectiveTransformProcessor.cs | 216 ++++++++++-------- .../Processors/Transforms/RotateTests.cs | 23 +- .../Transforms/ProjectiveTransformTests.cs | 2 +- tests/Images/External | 2 +- 4 files changed, 130 insertions(+), 113 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index 042ce2ff6..4c789ef02 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; @@ -75,28 +76,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.Sampler is NearestNeighborResampler) { - ParallelFor.WithConfiguration( - 0, - height, + ParallelHelper.IterateRows( + targetBounds, configuration, - y => - { - Span destRow = destination.GetPixelRowSpan(y); - - for (int x = 0; x < width; x++) + rows => { - var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); + for (int y = rows.Min; y < rows.Max; y++) + { + Span destRow = destination.GetPixelRowSpan(y); - float z = MathF.Max(v3.Z, Epsilon); - int px = (int)MathF.Round(v3.X / z); - int py = (int)MathF.Round(v3.Y / z); + for (int x = 0; x < width; x++) + { + var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); - if (sourceBounds.Contains(px, py)) - { - destRow[x] = source[px, py]; + float z = MathF.Max(v3.Z, Epsilon); + int px = (int)MathF.Round(v3.X / z); + int py = (int)MathF.Round(v3.Y / z); + + if (sourceBounds.Contains(px, py)) + { + destRow[x] = source[px, py]; + } + } } - } - }); + }); return; } @@ -121,92 +124,113 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms using (Buffer2D yBuffer = memoryAllocator.Allocate2D(yLength, height)) using (Buffer2D xBuffer = memoryAllocator.Allocate2D(xLength, height)) { - ParallelFor.WithConfiguration( - 0, - height, + ParallelHelper.IterateRows( + targetBounds, configuration, - y => - { - ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y)); - ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y)); - ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y)); - - for (int x = 0; x < width; x++) + rows => { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); - float z = MathF.Max(v3.Z, Epsilon); - - // Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable: - Vector4 point = new Vector4(v3.X, v3.Y, 0, 0) / z; - - // Clamp sampling pixel radial extents to the source image edges - Vector4 maxXY = point + radius; - Vector4 minXY = point - radius; - - // max, maxY, minX, minY - var extents = new Vector4( - MathF.Floor(maxXY.X + .5F), - MathF.Floor(maxXY.Y + .5F), - MathF.Ceiling(minXY.X - .5F), - MathF.Ceiling(minXY.Y - .5F)); - - int right = (int)extents.X; - int bottom = (int)extents.Y; - int left = (int)extents.Z; - int top = (int)extents.W; - - extents = Vector4.Clamp(extents, Vector4.Zero, maxSource); - - int maxX = (int)extents.X; - int maxY = (int)extents.Y; - int minX = (int)extents.Z; - int minY = (int)extents.W; - - if (minX == maxX || minY == maxY) - { - continue; - } - - // It appears these have to be calculated on-the-fly. - // Precalulating transformed weights would require prior knowledge of every transformed pixel location - // since they can be at sub-pixel positions on both axis. - // I've optimized where I can but am always open to suggestions. - if (yScale > 1 && xScale > 1) - { - CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ref ySpanRef, yLength); - CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, ref xSpanRef, xLength); - } - else + for (int y = rows.Min; y < rows.Max; y++) { - CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef); - CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef); - } + ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y)); + ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y)); + ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y)); - // Now multiply the results against the offsets - Vector4 sum = Vector4.Zero; - for (int yy = 0, j = minY; j <= maxY; j++, yy++) - { - float yWeight = Unsafe.Add(ref ySpanRef, yy); - - for (int xx = 0, i = minX; i <= maxX; i++, xx++) + for (int x = 0; x < width; x++) { - float xWeight = Unsafe.Add(ref xSpanRef, xx); - var vector = source[i, j].ToVector4(); - - // Values are first premultiplied to prevent darkening of edge pixels - Vector4 multiplied = vector.Premultiply(); - sum += multiplied * xWeight * yWeight; + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); + float z = MathF.Max(v3.Z, Epsilon); + + // Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable: + Vector4 point = new Vector4(v3.X, v3.Y, 0, 0) / z; + + // Clamp sampling pixel radial extents to the source image edges + Vector4 maxXY = point + radius; + Vector4 minXY = point - radius; + + // max, maxY, minX, minY + var extents = new Vector4( + MathF.Floor(maxXY.X + .5F), + MathF.Floor(maxXY.Y + .5F), + MathF.Ceiling(minXY.X - .5F), + MathF.Ceiling(minXY.Y - .5F)); + + int right = (int)extents.X; + int bottom = (int)extents.Y; + int left = (int)extents.Z; + int top = (int)extents.W; + + extents = Vector4.Clamp(extents, Vector4.Zero, maxSource); + + int maxX = (int)extents.X; + int maxY = (int)extents.Y; + int minX = (int)extents.Z; + int minY = (int)extents.W; + + if (minX == maxX || minY == maxY) + { + continue; + } + + // It appears these have to be calculated on-the-fly. + // Precalulating transformed weights would require prior knowledge of every transformed pixel location + // since they can be at sub-pixel positions on both axis. + // I've optimized where I can but am always open to suggestions. + if (yScale > 1 && xScale > 1) + { + CalculateWeightsDown( + top, + bottom, + minY, + maxY, + point.Y, + sampler, + yScale, + ref ySpanRef, + yLength); + + CalculateWeightsDown( + left, + right, + minX, + maxX, + point.X, + sampler, + xScale, + ref xSpanRef, + xLength); + } + else + { + CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef); + CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef); + } + + // Now multiply the results against the offsets + Vector4 sum = Vector4.Zero; + for (int yy = 0, j = minY; j <= maxY; j++, yy++) + { + float yWeight = Unsafe.Add(ref ySpanRef, yy); + + for (int xx = 0, i = minX; i <= maxX; i++, xx++) + { + float xWeight = Unsafe.Add(ref xSpanRef, xx); + var vector = source[i, j].ToVector4(); + + // Values are first premultiplied to prevent darkening of edge pixels + Vector4 multiplied = vector.Premultiply(); + sum += multiplied * xWeight * yWeight; + } + } + + ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); + + // Reverse the premultiplication + dest.PackFromVector4(sum.UnPremultiply()); } } - - ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); - - // Reverse the premultiplication - dest.PackFromVector4(sum.UnPremultiply()); - } - }); + }); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs index c0db205f9..7801c7143 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs @@ -7,7 +7,8 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public class RotateTests : FileTestBase + [GroupOutput("Transforms")] + public class RotateTests { public static readonly TheoryData RotateAngles = new TheoryData @@ -25,29 +26,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms }; [Theory] - [WithTestPatternImages(nameof(RotateAngles), 100, 50, DefaultPixelType)] - [WithTestPatternImages(nameof(RotateAngles), 50, 100, DefaultPixelType)] + [WithTestPatternImages(nameof(RotateAngles), 100, 50, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(RotateAngles), 50, 100, PixelTypes.Rgba32)] public void Rotate_WithAngle(TestImageProvider provider, float value) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Rotate(value)); - image.DebugSave(provider, value); - } + provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } [Theory] - [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, DefaultPixelType)] - [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, DefaultPixelType)] + [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, PixelTypes.Rgba32)] public void Rotate_WithRotateTypeEnum(TestImageProvider provider, RotateMode value) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Rotate(value)); - image.DebugSave(provider, value); - } + provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index 8cf9dd62f..5190a71e7 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.5f, 3); private ITestOutputHelper Output { get; } - + public static readonly TheoryData ResamplerNames = new TheoryData { nameof(KnownResamplers.Bicubic), diff --git a/tests/Images/External b/tests/Images/External index c1e14c0e4..7f4d2d64c 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit c1e14c0e431066c57585f255d3feb8d3a1860d50 +Subproject commit 7f4d2d64c6b820ca2b6827e6a8540a1013305ccf