Browse Source

Fix sampling offset

af/merge-core
James Jackson-South 8 years ago
parent
commit
bce9d38fe2
  1. 51
      src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
  2. 3
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  3. 3
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  4. 43
      tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs
  5. 48
      tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTest.cs

51
src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public bool Expand { get; set; } = true; public bool Expand { get; set; } = true;
@ -96,33 +96,58 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="source">The source image</param> /// <param name="source">The source image</param>
/// <param name="maxX">The maximum x value</param> /// <param name="maxX">The maximum x value</param>
/// <param name="maxY">The maximum y value</param> /// <param name="maxY">The maximum y value</param>
/// <param name="radius">The radius of the current sampling window</param>
/// <param name="windowX">The horizontal weights</param> /// <param name="windowX">The horizontal weights</param>
/// <param name="windowY">The vertical weights</param> /// <param name="windowY">The vertical weights</param>
/// <param name="point">The transformed position</param> /// <param name="point">The transformed position</param>
/// <returns>The <see cref="Vector4"/></returns> /// <returns>The <see cref="Vector4"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Vector4 ComputeWeightedSumAtPosition(ImageFrame<TPixel> source, int maxX, int maxY, ref WeightsWindow windowX, ref WeightsWindow windowY, ref Point point) protected Vector4 ComputeWeightedSumAtPosition(ImageFrame<TPixel> source, int maxX, int maxY, int radius, ref WeightsWindow windowX, ref WeightsWindow windowY, ref Point point)
{ {
ref float horizontalValues = ref windowX.GetStartReference(); ref float horizontalValues = ref windowX.GetStartReference();
ref float verticalValues = ref windowY.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; 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); float yweight = Unsafe.Add(ref verticalValues, yl);
int offsetY = y + point.Y;
offsetY = offsetY.Clamp(0, maxY);
for (int x = 0; x < xLength; x++) for (int x = left, xl = 0; x <= right; x++, xl++)
{ {
float xweight = Unsafe.Add(ref horizontalValues, x); float xweight = Unsafe.Add(ref horizontalValues, xl);
int offsetX = x + point.X;
offsetX = offsetX.Clamp(0, maxX);
float weight = yweight * xweight; float weight = yweight * xweight;
result += source[offsetX, offsetY].ToVector4() * weight; result += source[x, y].ToVector4() * weight;
} }
} }

3
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs

@ -111,6 +111,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
int maxX = source.Width - 1; int maxX = source.Width - 1;
int maxY = source.Height - 1; int maxY = source.Height - 1;
int radius = Math.Max((int)this.Sampler.Radius, 1);
Parallel.For( Parallel.For(
0, 0,
@ -127,7 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
WeightsWindow windowX = this.HorizontalWeights.Weights[transformedPoint.X]; WeightsWindow windowX = this.HorizontalWeights.Weights[transformedPoint.X];
WeightsWindow windowY = this.VerticalWeights.Weights[transformedPoint.Y]; 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]; ref TPixel dest = ref destRow[x];
dest.PackFromVector4(dXY); dest.PackFromVector4(dXY);
} }

3
src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs

@ -83,6 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
int maxX = source.Width - 1; int maxX = source.Width - 1;
int maxY = source.Height - 1; int maxY = source.Height - 1;
int radius = Math.Max((int)this.Sampler.Radius, 1);
Parallel.For( Parallel.For(
0, 0,
@ -99,7 +100,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
WeightsWindow windowX = this.HorizontalWeights.Weights[transformedPoint.X]; WeightsWindow windowX = this.HorizontalWeights.Weights[transformedPoint.X];
WeightsWindow windowY = this.VerticalWeights.Weights[transformedPoint.Y]; 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]; ref TPixel dest = ref destRow[x];
dest.PackFromVector4(dXY); dest.PackFromVector4(dXY);
} }

43
tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs

@ -29,24 +29,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
RotateType.Rotate270 RotateType.Rotate270
}; };
public static readonly TheoryData<string> ResmplerNames = new TheoryData<string> public static readonly TheoryData<string> ResamplerNames
{ = new TheoryData<string>
nameof(KnownResamplers.Bicubic), {
nameof(KnownResamplers.Box), nameof(KnownResamplers.Bicubic),
nameof(KnownResamplers.CatmullRom), nameof(KnownResamplers.Box),
nameof(KnownResamplers.Hermite), nameof(KnownResamplers.CatmullRom),
nameof(KnownResamplers.Lanczos2), nameof(KnownResamplers.Hermite),
nameof(KnownResamplers.Lanczos3), nameof(KnownResamplers.Lanczos2),
nameof(KnownResamplers.Lanczos5), nameof(KnownResamplers.Lanczos3),
nameof(KnownResamplers.Lanczos8), nameof(KnownResamplers.Lanczos5),
nameof(KnownResamplers.MitchellNetravali), nameof(KnownResamplers.Lanczos8),
nameof(KnownResamplers.NearestNeighbor), nameof(KnownResamplers.MitchellNetravali),
nameof(KnownResamplers.Robidoux), nameof(KnownResamplers.NearestNeighbor),
nameof(KnownResamplers.RobidouxSharp), nameof(KnownResamplers.Robidoux),
nameof(KnownResamplers.Spline), nameof(KnownResamplers.RobidouxSharp),
nameof(KnownResamplers.Triangle), nameof(KnownResamplers.Spline),
nameof(KnownResamplers.Welch), nameof(KnownResamplers.Triangle),
}; nameof(KnownResamplers.Welch),
};
[Theory] [Theory]
[WithTestPatternImages(nameof(RotateFloatValues), 100, 50, DefaultPixelType)] [WithTestPatternImages(nameof(RotateFloatValues), 100, 50, DefaultPixelType)]
@ -62,8 +63,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
} }
[Theory] [Theory]
[WithTestPatternImages(nameof(ResmplerNames), 100, 50, DefaultPixelType)] [WithTestPatternImages(nameof(ResamplerNames), 100, 50, DefaultPixelType)]
[WithTestPatternImages(nameof(ResmplerNames), 50, 100, DefaultPixelType)] [WithTestPatternImages(nameof(ResamplerNames), 50, 100, DefaultPixelType)]
public void RotateWithSampler<TPixel>(TestImageProvider<TPixel> provider, string resamplerName) public void RotateWithSampler<TPixel>(TestImageProvider<TPixel> provider, string resamplerName)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
@ -98,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
throw new Exception("Invalid property name!"); throw new Exception("Invalid property name!");
} }
return (IResampler) property.GetValue(null); return (IResampler)property.GetValue(null);
} }
} }
} }

48
tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTest.cs

@ -6,17 +6,41 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{ {
using System;
using System.Collections.Generic;
using System.Reflection;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
public class SkewTest : FileTestBase public class SkewTest : FileTestBase
{ {
public static readonly TheoryData<float, float> SkewValues public static readonly TheoryData<float, float> SkewValues
= new TheoryData<float, float> = new TheoryData<float, float>
{ {
{ 20, 10 }, { 20, 10 },
{ -20, -10 } { -20, -10 }
}; };
public static readonly List<string> ResamplerNames
= new List<string>
{
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] [Theory]
[WithFileCollection(nameof(DefaultFiles), nameof(SkewValues), DefaultPixelType)] [WithFileCollection(nameof(DefaultFiles), nameof(SkewValues), DefaultPixelType)]
public void ImageShouldSkew<TPixel>(TestImageProvider<TPixel> provider, float x, float y) public void ImageShouldSkew<TPixel>(TestImageProvider<TPixel> provider, float x, float y)
@ -34,11 +58,27 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
public void ImageShouldSkewWithSampler<TPixel>(TestImageProvider<TPixel> provider, float x, float y) public void ImageShouldSkewWithSampler<TPixel>(TestImageProvider<TPixel> provider, float x, float y)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage()) foreach (string resamplerName in ResamplerNames)
{
IResampler sampler = GetResampler(resamplerName);
using (Image<TPixel> 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)); throw new Exception("Invalid property name!");
image.DebugSave(provider, string.Join("_", x, y, "triangle"));
} }
return (IResampler)property.GetValue(null);
} }
} }
} }
Loading…
Cancel
Save