diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs index 9fcc3f4e4..4db2aacba 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } /// - /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image. + /// Gets or sets a value indicating whether to expand the canvas to fit the transformed image. /// public bool Expand { get; set; } = true; @@ -96,33 +96,58 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The source image /// The maximum x value /// The maximum y value + /// The radius of the current sampling window /// The horizontal weights /// The vertical weights /// The transformed position /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected Vector4 ComputeWeightedSumAtPosition(ImageFrame source, int maxX, int maxY, ref WeightsWindow windowX, ref WeightsWindow windowY, ref Point point) + protected Vector4 ComputeWeightedSumAtPosition(ImageFrame source, int maxX, int maxY, int radius, ref WeightsWindow windowX, ref WeightsWindow windowY, ref Point point) { ref float horizontalValues = ref windowX.GetStartReference(); ref float verticalValues = ref windowY.GetStartReference(); - int xLength = windowX.Length; - int yLength = windowY.Length; + + int left = point.X - radius; + int right = point.X + radius; + int top = point.Y - radius; + int bottom = point.Y + radius; + + // Faster than clamping + we know we are only looking in one direction + if (left < 0) + { + left = 0; + } + + if (top < 0) + { + top = 0; + } + + if (right > maxX) + { + right = maxX; + } + + if (bottom > maxY) + { + bottom = maxY; + } + Vector4 result = Vector4.Zero; - for (int y = 0; y < yLength; y++) + // We calculate our sample by iterating up-down/left-right from our transformed point. + // Ignoring the weight of outlying pixels is better for shape preservation on transforms such as skew with samplers that use larger radii. + // We don't offset our window index so that the weight compensates for the missing values + for (int y = top, yl = 0; y <= bottom; y++, yl++) { - float yweight = Unsafe.Add(ref verticalValues, y); - int offsetY = y + point.Y; - offsetY = offsetY.Clamp(0, maxY); + float yweight = Unsafe.Add(ref verticalValues, yl); - for (int x = 0; x < xLength; x++) + for (int x = left, xl = 0; x <= right; x++, xl++) { - float xweight = Unsafe.Add(ref horizontalValues, x); - int offsetX = x + point.X; - offsetX = offsetX.Clamp(0, maxX); + float xweight = Unsafe.Add(ref horizontalValues, xl); float weight = yweight * xweight; - result += source[offsetX, offsetY].ToVector4() * weight; + result += source[x, y].ToVector4() * weight; } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index 35ce8ce63..a1b181e69 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -111,6 +111,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int maxX = source.Width - 1; int maxY = source.Height - 1; + int radius = Math.Max((int)this.Sampler.Radius, 1); Parallel.For( 0, @@ -127,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors WeightsWindow windowX = this.HorizontalWeights.Weights[transformedPoint.X]; WeightsWindow windowY = this.VerticalWeights.Weights[transformedPoint.Y]; - Vector4 dXY = this.ComputeWeightedSumAtPosition(source, maxX, maxY, ref windowX, ref windowY, ref transformedPoint); + Vector4 dXY = this.ComputeWeightedSumAtPosition(source, maxX, maxY, radius, ref windowX, ref windowY, ref transformedPoint); ref TPixel dest = ref destRow[x]; dest.PackFromVector4(dXY); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index 0daf6acdd..0f67f724c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -83,6 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int maxX = source.Width - 1; int maxY = source.Height - 1; + int radius = Math.Max((int)this.Sampler.Radius, 1); Parallel.For( 0, @@ -99,7 +100,7 @@ namespace SixLabors.ImageSharp.Processing.Processors WeightsWindow windowX = this.HorizontalWeights.Weights[transformedPoint.X]; WeightsWindow windowY = this.VerticalWeights.Weights[transformedPoint.Y]; - Vector4 dXY = this.ComputeWeightedSumAtPosition(source, maxX, maxY, ref windowX, ref windowY, ref transformedPoint); + Vector4 dXY = this.ComputeWeightedSumAtPosition(source, maxX, maxY, radius, ref windowX, ref windowY, ref transformedPoint); ref TPixel dest = ref destRow[x]; dest.PackFromVector4(dXY); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs index c30770ada..ca29009bc 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs @@ -29,24 +29,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms RotateType.Rotate270 }; - public static readonly TheoryData ResmplerNames = new TheoryData - { - nameof(KnownResamplers.Bicubic), - nameof(KnownResamplers.Box), - nameof(KnownResamplers.CatmullRom), - nameof(KnownResamplers.Hermite), - nameof(KnownResamplers.Lanczos2), - nameof(KnownResamplers.Lanczos3), - nameof(KnownResamplers.Lanczos5), - nameof(KnownResamplers.Lanczos8), - nameof(KnownResamplers.MitchellNetravali), - nameof(KnownResamplers.NearestNeighbor), - nameof(KnownResamplers.Robidoux), - nameof(KnownResamplers.RobidouxSharp), - nameof(KnownResamplers.Spline), - nameof(KnownResamplers.Triangle), - nameof(KnownResamplers.Welch), - }; + public static readonly TheoryData ResamplerNames + = new TheoryData + { + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.CatmullRom), + nameof(KnownResamplers.Hermite), + nameof(KnownResamplers.Lanczos2), + nameof(KnownResamplers.Lanczos3), + nameof(KnownResamplers.Lanczos5), + nameof(KnownResamplers.Lanczos8), + nameof(KnownResamplers.MitchellNetravali), + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Robidoux), + nameof(KnownResamplers.RobidouxSharp), + nameof(KnownResamplers.Spline), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Welch), + }; [Theory] [WithTestPatternImages(nameof(RotateFloatValues), 100, 50, DefaultPixelType)] @@ -62,8 +63,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } [Theory] - [WithTestPatternImages(nameof(ResmplerNames), 100, 50, DefaultPixelType)] - [WithTestPatternImages(nameof(ResmplerNames), 50, 100, DefaultPixelType)] + [WithTestPatternImages(nameof(ResamplerNames), 100, 50, DefaultPixelType)] + [WithTestPatternImages(nameof(ResamplerNames), 50, 100, DefaultPixelType)] public void RotateWithSampler(TestImageProvider provider, string resamplerName) where TPixel : struct, IPixel { @@ -98,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms throw new Exception("Invalid property name!"); } - return (IResampler) property.GetValue(null); + return (IResampler)property.GetValue(null); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTest.cs index 6e0d65149..8e57327be 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTest.cs @@ -6,17 +6,41 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + using System; + using System.Collections.Generic; + using System.Reflection; + using SixLabors.ImageSharp.Processing; public class SkewTest : FileTestBase { public static readonly TheoryData SkewValues - = new TheoryData + = new TheoryData { { 20, 10 }, { -20, -10 } }; + public static readonly List ResamplerNames + = new List + { + nameof(KnownResamplers.Bicubic), + nameof(KnownResamplers.Box), + nameof(KnownResamplers.CatmullRom), + nameof(KnownResamplers.Hermite), + nameof(KnownResamplers.Lanczos2), + nameof(KnownResamplers.Lanczos3), + nameof(KnownResamplers.Lanczos5), + nameof(KnownResamplers.Lanczos8), + nameof(KnownResamplers.MitchellNetravali), + nameof(KnownResamplers.NearestNeighbor), + nameof(KnownResamplers.Robidoux), + nameof(KnownResamplers.RobidouxSharp), + nameof(KnownResamplers.Spline), + nameof(KnownResamplers.Triangle), + nameof(KnownResamplers.Welch), + }; + [Theory] [WithFileCollection(nameof(DefaultFiles), nameof(SkewValues), DefaultPixelType)] public void ImageShouldSkew(TestImageProvider provider, float x, float y) @@ -34,11 +58,27 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public void ImageShouldSkewWithSampler(TestImageProvider provider, float x, float y) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) + foreach (string resamplerName in ResamplerNames) + { + IResampler sampler = GetResampler(resamplerName); + using (Image image = provider.GetImage()) + { + image.Mutate(i => i.Skew(x, y, sampler)); + image.DebugSave(provider, string.Join("_", x, y, resamplerName)); + } + } + } + + private static IResampler GetResampler(string name) + { + PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); + + if (property == null) { - image.Mutate(i => i.Skew(x, y, KnownResamplers.Triangle)); - image.DebugSave(provider, string.Join("_", x, y, "triangle")); + throw new Exception("Invalid property name!"); } + + return (IResampler)property.GetValue(null); } } } \ No newline at end of file