Browse Source

Fix affine transforms and cleanup

af/merge-core
James Jackson-South 9 years ago
parent
commit
37370aa089
  1. 32
      src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
  2. 14
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  3. 10
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  4. 5
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  5. 2
      src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs
  6. 96
      src/ImageSharp/Processing/Transforms/Resamplers/KnownResamplers.cs
  7. 8
      src/ImageSharp/Processing/Transforms/Resize.cs
  8. 31
      src/ImageSharp/Processing/Transforms/Rotate.cs
  9. 33
      src/ImageSharp/Processing/Transforms/Skew.cs
  10. 2
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
  11. 37
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  12. 13
      tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs
  13. 14
      tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTest.cs

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -18,15 +19,12 @@ namespace SixLabors.ImageSharp.Processing.Processors
internal abstract class AffineProcessor<TPixel> : ResamplingWeightedProcessor<TPixel> internal abstract class AffineProcessor<TPixel> : ResamplingWeightedProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: Move to constants somewhere else to prevent generic type duplication.
private static readonly Rectangle DefaultRectangle = new Rectangle(0, 0, 1, 1);
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AffineProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="AffineProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="sampler">The sampler to perform the resize operation.</param> /// <param name="sampler">The sampler to perform the resize operation.</param>
protected AffineProcessor(IResampler sampler) protected AffineProcessor(IResampler sampler)
: base(sampler, 1, 1, DefaultRectangle) // Hack to prevent Guard throwing in base, we always set the canvas : base(sampler, 1, 1, Rectangles.DefaultRectangle) // Hack to prevent Guard throwing in base, we always set the canvas
{ {
} }
@ -47,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="sourceRectangle">The source rectangle.</param> /// <param name="sourceRectangle">The source rectangle.</param>
protected virtual void CreateNewCanvas(Rectangle sourceRectangle) protected virtual void CreateNewCanvas(Rectangle sourceRectangle)
{ {
if (this.ResizeRectangle == DefaultRectangle) if (this.ResizeRectangle == Rectangles.DefaultRectangle)
{ {
if (this.Expand) if (this.Expand)
{ {
@ -82,15 +80,14 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Gets a transform matrix adjusted to center upon the target image bounds. /// Gets a transform matrix adjusted to center upon the target image bounds.
/// </summary> /// </summary>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="matrix">The transform matrix.</param>
/// <returns> /// <returns>
/// The <see cref="Matrix3x2"/>. /// The <see cref="Matrix3x2"/>.
/// </returns> /// </returns>
protected Matrix3x2 GetCenteredMatrix(ImageFrame<TPixel> source, Matrix3x2 matrix) protected Matrix3x2 GetCenteredMatrix(ImageFrame<TPixel> source)
{ {
var translationToTargetCenter = Matrix3x2.CreateTranslation(-this.ResizeRectangle.Width * .5F, -this.ResizeRectangle.Height * .5F); var translationToTargetCenter = Matrix3x2.CreateTranslation(-this.ResizeRectangle.Width * .5F, -this.ResizeRectangle.Height * .5F);
var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F);
return (translationToTargetCenter * matrix) * translateToSourceCenter; return (translationToTargetCenter * this.CreateProcessingMatrix()) * translateToSourceCenter;
} }
/// <summary> /// <summary>
@ -108,25 +105,20 @@ namespace SixLabors.ImageSharp.Processing.Processors
{ {
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 xLeft = windowX.Left;
int yLeft = windowY.Left;
int xLength = windowX.Length; int xLength = windowX.Length;
int yLength = windowY.Length; int yLength = windowY.Length;
Vector4 result = Vector4.Zero; Vector4 result = Vector4.Zero;
// TODO: Fix this.
// The output for skew is shrunken, offset, with right/bottom banding.
// For rotate values are offset
for (int y = 0; y < yLength; y++) for (int y = 0; y < yLength; y++)
{ {
float yweight = Unsafe.Add(ref verticalValues, y); float yweight = Unsafe.Add(ref verticalValues, y);
int offsetY = yLeft + y + point.Y; int offsetY = y + point.Y;
offsetY = offsetY.Clamp(0, maxY); offsetY = offsetY.Clamp(0, maxY);
for (int x = 0; x < xLength; x++) for (int x = 0; x < xLength; x++)
{ {
float xweight = Unsafe.Add(ref horizontalValues, x); float xweight = Unsafe.Add(ref horizontalValues, x);
int offsetX = xLeft + x + point.X; int offsetX = x + point.X;
offsetX = offsetX.Clamp(0, maxX); offsetX = offsetX.Clamp(0, maxX);
float weight = yweight * xweight; float weight = yweight * xweight;
@ -137,4 +129,14 @@ namespace SixLabors.ImageSharp.Processing.Processors
return result; return result;
} }
} }
/// <summary>
/// Contains a static rectangle used for comparison when creating a new canvas.
/// We do this so we can inherit from the resampling weights class and pass the guard in the constructor and also avoid creating a new rectangle each time.
/// </summary>
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "I'm using this only here to prevent duplication in generic types.")]
internal static class Rectangles
{
public static Rectangle DefaultRectangle { get; } = new Rectangle(0, 0, 1, 1);
}
} }

14
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -53,16 +53,12 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <inheritdoc/> /// <inheritdoc/>
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{ {
Configuration config = source.GetConfiguration(); // We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames =
source.Frames.Select(x => new ImageFrame<TPixel>(this.Width, this.Height, x.MetaData.Clone()));
// We will always be creating the clone even for mutate because thats the way this base processor works // Use the overload to prevent an extra frame being added
// ------------ return new Image<TPixel>(source.GetConfiguration(), source.MetaData.Clone(), frames);
// For resize we know we are going to populate every pixel with fresh data and we want a different target size so
// let's manually clone an empty set of images at the correct target and then have the base class processs them in turn.
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(x => new ImageFrame<TPixel>(this.Width, this.Height, x.MetaData.Clone())); // this will create places holders
var image = new Image<TPixel>(config, source.MetaData.Clone(), frames); // base the place holder images in to prevet a extra frame being added
return image;
} }
/// <inheritdoc/> /// <inheritdoc/>

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

@ -25,14 +25,14 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class.
/// </summary> /// </summary>
public RotateProcessor() public RotateProcessor()
: base(new BicubicResampler()) : base(KnownResamplers.NearestNeighbor)
{ {
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="sampler">The sampler to perform the resize operation.</param> /// <param name="sampler">The sampler to perform the rotating operation.</param>
public RotateProcessor(IResampler sampler) public RotateProcessor(IResampler sampler)
: base(sampler) : base(sampler)
{ {
@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
{ {
if (this.transformMatrix == default(Matrix3x2)) if (this.transformMatrix == default(Matrix3x2))
{ {
this.transformMatrix = Matrix3x2Extensions.CreateRotationDegrees(-this.Angle, new Point(0, 0)); this.transformMatrix = Matrix3x2Extensions.CreateRotationDegrees(-this.Angle, PointF.Empty);
} }
return this.transformMatrix; return this.transformMatrix;
@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
int height = this.ResizeRectangle.Height; int height = this.ResizeRectangle.Height;
int width = this.ResizeRectangle.Width; int width = this.ResizeRectangle.Width;
Matrix3x2 matrix = this.GetCenteredMatrix(source, this.CreateProcessingMatrix()); Matrix3x2 matrix = this.GetCenteredMatrix(source);
Rectangle sourceBounds = source.Bounds(); Rectangle sourceBounds = source.Bounds();
if (this.Sampler is NearestNeighborResampler) if (this.Sampler is NearestNeighborResampler)
@ -99,7 +99,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
var transformedPoint = Point.Rotate(new Point(x, y), matrix); var transformedPoint = Point.Rotate(new Point(x, y), matrix);
if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y)) if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y))
{ {
destRow[x] = source[transformedPoint.X, transformedPoint.Y]; destRow[x] = source[transformedPoint.X, transformedPoint.Y];
@ -127,6 +126,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, ref windowX, ref windowY, ref transformedPoint);
ref TPixel dest = ref destRow[x]; ref TPixel dest = ref destRow[x];
dest.PackFromVector4(dXY); dest.PackFromVector4(dXY);

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

@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
{ {
if (this.transformMatrix == default(Matrix3x2)) if (this.transformMatrix == default(Matrix3x2))
{ {
this.transformMatrix = Matrix3x2Extensions.CreateSkewDegrees(-this.AngleX, -this.AngleY, new Point(0, 0)); this.transformMatrix = Matrix3x2Extensions.CreateSkewDegrees(-this.AngleX, -this.AngleY, PointF.Empty);
} }
return this.transformMatrix; return this.transformMatrix;
@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
{ {
int height = this.ResizeRectangle.Height; int height = this.ResizeRectangle.Height;
int width = this.ResizeRectangle.Width; int width = this.ResizeRectangle.Width;
Matrix3x2 matrix = this.GetCenteredMatrix(source, this.CreateProcessingMatrix()); Matrix3x2 matrix = this.GetCenteredMatrix(source);
Rectangle sourceBounds = source.Bounds(); Rectangle sourceBounds = source.Bounds();
if (this.Sampler is NearestNeighborResampler) if (this.Sampler is NearestNeighborResampler)
@ -71,7 +71,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
var transformedPoint = Point.Skew(new Point(x, y), matrix); var transformedPoint = Point.Skew(new Point(x, y), matrix);
if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y)) if (sourceBounds.Contains(transformedPoint.X, transformedPoint.Y))
{ {
destRow[x] = source[transformedPoint.X, transformedPoint.Y]; destRow[x] = source[transformedPoint.X, transformedPoint.Y];

2
src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing
/// <summary> /// <summary>
/// Gets or sets the sampler to perform the resize operation. /// Gets or sets the sampler to perform the resize operation.
/// </summary> /// </summary>
public IResampler Sampler { get; set; } = new BicubicResampler(); public IResampler Sampler { get; set; } = KnownResamplers.Bicubic;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to compress /// Gets or sets a value indicating whether to compress

96
src/ImageSharp/Processing/Transforms/Resamplers/KnownResamplers.cs

@ -0,0 +1,96 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Contains reusable static instances of known resampling algorithms
/// </summary>
public static class KnownResamplers
{
/// <summary>
/// Gets the Bicubic sampler that implements the bicubic kernel algorithm W(x)
/// </summary>
public static IResampler Bicubic { get; } = new BicubicResampler();
/// <summary>
/// Gets the Box sampler that implements the box algorithm. Similar to nearest neighbor when upscaling.
/// When downscaling the pixels will average, merging pixels together.
/// </summary>
public static IResampler Box { get; } = new BoxResampler();
/// <summary>
/// Gets the Catmull-Rom sampler, a well known standard Cubic Filter often used as a interpolation function
/// </summary>
public static IResampler CatmullRom { get; } = new CatmullRomResampler();
/// <summary>
/// Gets the Hermite sampler. A type of smoothed triangular interpolation filter that rounds off strong edges while
/// preserving flat 'color levels' in the original image.
/// </summary>
public static IResampler Hermite { get; } = new HermiteResampler();
/// <summary>
/// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 2 pixels.
/// This algorithm provides sharpened results when compared to others when downsampling.
/// </summary>
public static IResampler Lanczos2 { get; } = new Lanczos2Resampler();
/// <summary>
/// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 3 pixels
/// This algorithm provides sharpened results when compared to others when downsampling.
/// </summary>
public static IResampler Lanczos3 { get; } = new Lanczos3Resampler();
/// <summary>
/// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 5 pixels
/// This algorithm provides sharpened results when compared to others when downsampling.
/// </summary>
public static IResampler Lanczos5 { get; } = new Lanczos5Resampler();
/// <summary>
/// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 8 pixels
/// This algorithm provides sharpened results when compared to others when downsampling.
/// </summary>
public static IResampler Lanczos8 { get; } = new Lanczos8Resampler();
/// <summary>
/// Gets the Mitchell-Netravali sampler. This seperable cubic algorithm yields a very good equilibrium between
/// detail preservation (sharpness) and smoothness.
/// </summary>
public static IResampler MitchellNetravali { get; } = new MitchellNetravaliResampler();
/// <summary>
/// Gets the Nearest-Neighbour sampler that implements the nearest neighbor algorithm. This uses a very fast, unscaled filter
/// which will select the closest pixel to the new pixels position.
/// </summary>
public static IResampler NearestNeighbor { get; } = new NearestNeighborResampler();
/// <summary>
/// Gets the Robidoux sampler. This algorithm developed by Nicolas Robidoux providing a very good equilibrium between
/// detail preservation (sharpness) and smoothness comprable to <see cref="MitchellNetravali"/>.
/// </summary>
public static IResampler Robidoux { get; } = new RobidouxResampler();
/// <summary>
/// Gets the Robidoux Sharp sampler. A sharpend form of the <see cref="Robidoux"/> sampler
/// </summary>
public static IResampler RobidouxSharp { get; } = new RobidouxResampler();
/// <summary>
/// Gets the Spline sampler. A seperable cubic algorithm similar to <see cref="MitchellNetravali"/> but yielding smoother results.
/// </summary>
public static IResampler Spline { get; } = new SplineResampler();
/// <summary>
/// Gets the Triangle sampler, otherwise known as Bilinear. This interpolation algorithm can be used where perfect image transformation
/// with pixel matching is impossible, so that one can calculate and assign appropriate intensity values to pixels
/// </summary>
public static IResampler Triangle { get; } = new TriangleResampler();
/// <summary>
/// Gets the Welch sampler. A high speed algorthm that delivers very sharpened results.
/// </summary>
public static IResampler Welch { get; } = new WelchResampler();
}
}

8
src/ImageSharp/Processing/Transforms/Resize.cs

@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, Size size) public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, Size size)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Resize(source, size.Width, size.Height, new BicubicResampler(), false); return Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false);
} }
/// <summary> /// <summary>
@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, Size size, bool compand) public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, Size size, bool compand)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Resize(source, size.Width, size.Height, new BicubicResampler(), compand); return Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand);
} }
/// <summary> /// <summary>
@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height) public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Resize(source, width, height, new BicubicResampler(), false); return Resize(source, width, height, KnownResamplers.Bicubic, false);
} }
/// <summary> /// <summary>
@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp
public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height, bool compand) public static IImageProcessingContext<TPixel> Resize<TPixel>(this IImageProcessingContext<TPixel> source, int width, int height, bool compand)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Resize(source, width, height, new BicubicResampler(), compand); return Resize(source, width, height, KnownResamplers.Bicubic, compand);
} }
/// <summary> /// <summary>

31
src/ImageSharp/Processing/Transforms/Rotate.cs

@ -21,9 +21,19 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees) public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ => Rotate(source, degrees, true);
return Rotate(source, degrees, true);
} /// <summary>
/// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to rotate.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees, IResampler sampler)
where TPixel : struct, IPixel<TPixel>
=> Rotate(source, degrees, sampler, true);
/// <summary> /// <summary>
/// Rotates and flips an image by the given instructions. /// Rotates and flips an image by the given instructions.
@ -46,6 +56,19 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees, bool expand) public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees, bool expand)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new RotateProcessor<TPixel>(new BicubicResampler()) { Angle = degrees, Expand = expand }); => Rotate(source, degrees, KnownResamplers.NearestNeighbor, expand);
/// <summary>
/// Rotates an image by the given angle in degrees using the specified sampling algorithm.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to rotate.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <param name="expand">Whether to expand the image to fit the rotated result.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Rotate<TPixel>(this IImageProcessingContext<TPixel> source, float degrees, IResampler sampler, bool expand)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new RotateProcessor<TPixel>(sampler) { Angle = degrees, Expand = expand });
} }
} }

33
src/ImageSharp/Processing/Transforms/Skew.cs

@ -22,9 +22,20 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY) public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ => Skew(source, degreesX, degreesY, true);
return Skew(source, degreesX, degreesY, true);
} /// <summary>
/// Skews an image by the given angles in degrees using the given sampler, expanding the image to fit the skewed result.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to skew.</param>
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
/// <param name="degreesY">The angle in degrees to perform the rotation along the y-axis.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY, IResampler sampler)
where TPixel : struct, IPixel<TPixel>
=> Skew(source, degreesX, degreesY, sampler, true);
/// <summary> /// <summary>
/// Skews an image by the given angles in degrees. /// Skews an image by the given angles in degrees.
@ -37,6 +48,20 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY, bool expand) public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY, bool expand)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new SkewProcessor<TPixel>(new BicubicResampler()) { AngleX = degreesX, AngleY = degreesY, Expand = expand }); => Skew(source, degreesX, degreesY, KnownResamplers.NearestNeighbor, expand);
/// <summary>
/// Skews an image by the given angles in degrees using the specified sampling algorithm.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to skew.</param>
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
/// <param name="degreesY">The angle in degrees to perform the rotation along the y-axis.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <param name="expand">Whether to expand the image to fit the skewed result.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Skew<TPixel>(this IImageProcessingContext<TPixel> source, float degreesX, float degreesY, IResampler sampler, bool expand)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new SkewProcessor<TPixel>(sampler) { AngleX = degreesX, AngleY = degreesY, Expand = expand });
} }
} }

2
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs

@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
// [Fact] // [Fact]
public void PrintWeightsData() public void PrintWeightsData()
{ {
var proc = new ResizeProcessor<Rgba32>(new BicubicResampler(), 200, 200); var proc = new ResizeProcessor<Rgba32>(KnownResamplers.Bicubic, 200, 200);
ResamplingWeightedProcessor<Rgba32>.WeightsBuffer weights = proc.PrecomputeWeights(200, 500); ResamplingWeightedProcessor<Rgba32>.WeightsBuffer weights = proc.PrecomputeWeights(200, 500);

37
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -20,19 +20,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
public static readonly TheoryData<string, IResampler> AllReSamplers = public static readonly TheoryData<string, IResampler> AllReSamplers =
new TheoryData<string, IResampler> new TheoryData<string, IResampler>
{ {
{ "Bicubic", new BicubicResampler() }, { "Bicubic", KnownResamplers.Bicubic },
{ "Triangle", new TriangleResampler() }, { "Triangle", KnownResamplers.Triangle},
{ "NearestNeighbor", new NearestNeighborResampler() }, { "NearestNeighbor", KnownResamplers.NearestNeighbor },
{ "Box", new BoxResampler() }, { "Box", KnownResamplers.Box },
{ "Lanczos3", new Lanczos3Resampler() }, { "Lanczos2", KnownResamplers.Lanczos2 },
{ "Lanczos5", new Lanczos5Resampler() }, { "Lanczos3", KnownResamplers.Lanczos3 },
{ "MitchellNetravali", new MitchellNetravaliResampler() }, { "Lanczos5", KnownResamplers.Lanczos5 },
{ "Lanczos8", new Lanczos8Resampler() }, { "MitchellNetravali", KnownResamplers.MitchellNetravali },
{ "Hermite", new HermiteResampler() }, { "Lanczos8", KnownResamplers.Lanczos8 },
{ "Spline", new SplineResampler() }, { "Hermite", KnownResamplers.Hermite },
{ "Robidoux", new RobidouxResampler() }, { "Spline", KnownResamplers.Spline },
{ "RobidouxSharp", new RobidouxSharpResampler() }, { "Robidoux", KnownResamplers.Robidoux },
{ "Welch", new WelchResampler() } { "RobidouxSharp", KnownResamplers.RobidouxSharp },
{ "Welch", KnownResamplers.Welch }
}; };
[Theory] [Theory]
@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
var sourceRectangle = new Rectangle(image.Width / 8, image.Height / 8, image.Width / 4, image.Height / 4); var sourceRectangle = new Rectangle(image.Width / 8, image.Height / 8, image.Width / 4, image.Height / 4);
var destRectangle = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); var destRectangle = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2);
image.Mutate(x => x.Resize(image.Width, image.Height, new BicubicResampler(), sourceRectangle, destRectangle, false)); image.Mutate(x => x.Resize(image.Width, image.Height, KnownResamplers.Bicubic, sourceRectangle, destRectangle, false));
image.DebugSave(provider); image.DebugSave(provider);
image.CompareToReferenceOutput(provider); image.CompareToReferenceOutput(provider);
@ -286,7 +287,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[InlineData(2, 0)] [InlineData(2, 0)]
public static void BicubicWindowOscillatesCorrectly(float x, float expected) public static void BicubicWindowOscillatesCorrectly(float x, float expected)
{ {
var sampler = new BicubicResampler(); var sampler = KnownResamplers.Bicubic;
float result = sampler.GetValue(x); float result = sampler.GetValue(x);
Assert.Equal(result, expected); Assert.Equal(result, expected);
@ -300,7 +301,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[InlineData(2, 0)] [InlineData(2, 0)]
public static void TriangleWindowOscillatesCorrectly(float x, float expected) public static void TriangleWindowOscillatesCorrectly(float x, float expected)
{ {
var sampler = new TriangleResampler(); var sampler = KnownResamplers.Triangle;
float result = sampler.GetValue(x); float result = sampler.GetValue(x);
Assert.Equal(result, expected); Assert.Equal(result, expected);
@ -314,7 +315,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[InlineData(2, 0)] [InlineData(2, 0)]
public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) public static void Lanczos3WindowOscillatesCorrectly(float x, float expected)
{ {
var sampler = new Lanczos3Resampler(); var sampler = KnownResamplers.Lanczos3;
float result = sampler.GetValue(x); float result = sampler.GetValue(x);
Assert.Equal(result, expected); Assert.Equal(result, expected);
@ -328,7 +329,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[InlineData(4, 0)] [InlineData(4, 0)]
public static void Lanczos5WindowOscillatesCorrectly(float x, float expected) public static void Lanczos5WindowOscillatesCorrectly(float x, float expected)
{ {
var sampler = new Lanczos5Resampler(); var sampler = KnownResamplers.Lanczos5;
float result = sampler.GetValue(x); float result = sampler.GetValue(x);
Assert.Equal(result, expected); Assert.Equal(result, expected);

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

@ -39,6 +39,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
} }
} }
[Theory]
[WithTestPatternImages(nameof(RotateFloatValues), 100, 50, DefaultPixelType)]
[WithTestPatternImages(nameof(RotateFloatValues), 50, 100, DefaultPixelType)]
public void RotateWithSampler<TPixel>(TestImageProvider<TPixel> provider, float value)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Rotate(value, KnownResamplers.Triangle));
image.DebugSave(provider, string.Join("_", value, "triangle"));
}
}
[Theory] [Theory]
[WithTestPatternImages(nameof(RotateEnumValues), 100, 50, DefaultPixelType)] [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, DefaultPixelType)]
[WithTestPatternImages(nameof(RotateEnumValues), 50, 100, DefaultPixelType)] [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, DefaultPixelType)]

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

@ -6,6 +6,8 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{ {
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
@ -26,5 +28,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
image.DebugSave(provider, string.Join("_", x, y)); image.DebugSave(provider, string.Join("_", x, y));
} }
} }
[Theory]
[WithFileCollection(nameof(DefaultFiles), nameof(SkewValues), DefaultPixelType)]
public void ImageShouldSkewWithSampler<TPixel>(TestImageProvider<TPixel> provider, float x, float y)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(i => i.Skew(x, y, KnownResamplers.Triangle));
image.DebugSave(provider, string.Join("_", x, y, "triangle"));
}
}
} }
} }
Loading…
Cancel
Save