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>
/// 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>
public bool Expand { get; set; } = true;
@ -96,33 +96,58 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="source">The source image</param>
/// <param name="maxX">The maximum x 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="windowY">The vertical weights</param>
/// <param name="point">The transformed position</param>
/// <returns>The <see cref="Vector4"/></returns>
[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 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;
}
}

3
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);
}

3
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);
}

43
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<string> ResmplerNames = new TheoryData<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),
};
public static readonly TheoryData<string> ResamplerNames
= new TheoryData<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]
[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<TPixel>(TestImageProvider<TPixel> provider, string resamplerName)
where TPixel : struct, IPixel<TPixel>
{
@ -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);
}
}
}

48
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<float, float> SkewValues
= new TheoryData<float, float>
= new TheoryData<float, float>
{
{ 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]
[WithFileCollection(nameof(DefaultFiles), nameof(SkewValues), DefaultPixelType)]
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)
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));
image.DebugSave(provider, string.Join("_", x, y, "triangle"));
throw new Exception("Invalid property name!");
}
return (IResampler)property.GetValue(null);
}
}
}
Loading…
Cancel
Save