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