Browse Source

Merge branch 'master' into sp/refactoring-and-tweaks-2

pull/1574/head
Sergio Pedri 6 years ago
parent
commit
81f235d232
  1. 29
      src/ImageSharp/Common/Helpers/Guard.cs
  2. 34
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  3. 12
      src/ImageSharp/Processing/AffineTransformBuilder.cs
  4. 4
      src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs
  5. 36
      src/ImageSharp/Processing/KnownResamplers.cs
  6. 18
      src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
  7. 188
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  8. 2
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
  9. 14
      src/ImageSharp/Processing/Processors/Transforms/IResampler.cs
  10. 23
      src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs
  11. 8
      src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs
  12. 234
      src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
  13. 0
      src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs
  14. 0
      src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs
  15. 0
      src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs
  16. 0
      src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs
  17. 104
      src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs
  18. 8
      src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs
  19. 156
      src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
  20. 4
      src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs
  21. 0
      src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs
  22. 4
      src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs
  23. 24
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs
  24. 16
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs
  25. 26
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs
  26. 112
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs
  27. 25
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs
  28. 32
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs
  29. 32
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs
  30. 32
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs
  31. 32
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs
  32. 68
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs
  33. 24
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs
  34. 21
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs
  35. 24
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs
  36. 24
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs
  37. 24
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs
  38. 16
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs
  39. 16
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs
  40. 17
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
  41. 17
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
  42. 55
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  43. 17
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
  44. 201
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  45. 3
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  46. 160
      src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs
  47. 13
      src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs
  48. 12
      src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
  49. 26
      tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs
  50. 42
      tests/ImageSharp.Benchmarks/Samplers/Rotate.cs
  51. 43
      tests/ImageSharp.Benchmarks/Samplers/Skew.cs
  52. 3
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
  53. 122
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
  54. 4
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  55. 8
      tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs
  56. 16
      tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs
  57. 4
      tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs
  58. 2
      tests/Images/External

29
src/ImageSharp/Common/Helpers/Guard.cs

@ -0,0 +1,29 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp;
namespace SixLabors
{
internal static partial class Guard
{
/// <summary>
/// Ensures that the value is a value type.
/// </summary>
/// <param name="value">The target object, which cannot be null.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException"><paramref name="value"/> is not a value type.</exception>
[MethodImpl(InliningOptions.ShortMethod)]
public static void MustBeValueType<TValue>(TValue value, string parameterName)
{
if (!value.GetType().GetTypeInfo().IsValueType)
{
ThrowArgumentException("Type must be a struct.", parameterName);
}
}
}
}

34
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -242,40 +242,6 @@ namespace SixLabors.ImageSharp
return 1F; return 1F;
} }
/// <summary>
/// Returns the result of a B-C filter against the given value.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
/// </summary>
/// <param name="x">The value to process.</param>
/// <param name="b">The B-Spline curve variable.</param>
/// <param name="c">The Cardinal curve variable.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float GetBcValue(float x, float b, float c)
{
if (x < 0F)
{
x = -x;
}
float temp = x * x;
if (x < 1F)
{
x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b));
return x / 6F;
}
if (x < 2F)
{
x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c));
return x / 6F;
}
return 0F;
}
/// <summary> /// <summary>
/// Gets the bounding <see cref="Rectangle"/> from the given points. /// Gets the bounding <see cref="Rectangle"/> from the given points.
/// </summary> /// </summary>

12
src/ImageSharp/Processing/AffineTransformBuilder.cs

@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radians">The amount of rotation, in radians.</param> /// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns> /// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependRotationRadians(float radians) public AffineTransformBuilder PrependRotationRadians(float radians)
=> this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); => this.Prepend(size => TransformUtilities.CreateRotationMatrixRadians(radians, size));
/// <summary> /// <summary>
/// Prepends a rotation matrix using the given rotation in degrees at the given origin. /// Prepends a rotation matrix using the given rotation in degrees at the given origin.
@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radians">The amount of rotation, in radians.</param> /// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns> /// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendRotationRadians(float radians) public AffineTransformBuilder AppendRotationRadians(float radians)
=> this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); => this.Append(size => TransformUtilities.CreateRotationMatrixRadians(radians, size));
/// <summary> /// <summary>
/// Appends a rotation matrix using the given rotation in degrees at the given origin. /// Appends a rotation matrix using the given rotation in degrees at the given origin.
@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="degreesY">The Y angle, in degrees.</param> /// <param name="degreesY">The Y angle, in degrees.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns> /// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY)
=> this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); => this.Prepend(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size));
/// <summary> /// <summary>
/// Prepends a centered skew matrix from the give angles in radians. /// Prepends a centered skew matrix from the give angles in radians.
@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radiansY">The Y angle, in radians.</param> /// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns> /// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY)
=> this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); => this.Prepend(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size));
/// <summary> /// <summary>
/// Prepends a skew matrix using the given angles in degrees at the given origin. /// Prepends a skew matrix using the given angles in degrees at the given origin.
@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="degreesY">The Y angle, in degrees.</param> /// <param name="degreesY">The Y angle, in degrees.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns> /// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY)
=> this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); => this.Append(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size));
/// <summary> /// <summary>
/// Appends a centered skew matrix from the give angles in radians. /// Appends a centered skew matrix from the give angles in radians.
@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radiansY">The Y angle, in radians.</param> /// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns> /// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY)
=> this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); => this.Append(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size));
/// <summary> /// <summary>
/// Appends a skew matrix using the given angles in degrees at the given origin. /// Appends a skew matrix using the given angles in degrees at the given origin.

4
src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs

@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing
IResampler sampler) IResampler sampler)
{ {
Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); Matrix3x2 transform = builder.BuildMatrix(sourceRectangle);
Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); Size targetDimensions = TransformUtilities.GetTransformedSize(sourceRectangle.Size, transform);
return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler);
} }
@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing
IResampler sampler) IResampler sampler)
{ {
Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); Matrix4x4 transform = builder.BuildMatrix(sourceRectangle);
Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); Size targetDimensions = TransformUtilities.GetTransformedSize(sourceRectangle.Size, transform);
return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler);
} }

36
src/ImageSharp/Processing/KnownResamplers.cs

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

18
src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs

@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
} }
/// <summary> /// <summary>
/// Gets the size of the target image. /// Gets the size of the destination image.
/// </summary> /// </summary>
/// <returns>The <see cref="Size"/>.</returns> /// <returns>The <see cref="Size"/>.</returns>
protected abstract Size GetTargetSize(); protected abstract Size GetDestinationSize();
/// <summary> /// <summary>
/// This method is called before the process is applied to prepare the processor. /// This method is called before the process is applied to prepare the processor.
@ -168,21 +168,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
private Image<TPixel> CreateTarget() private Image<TPixel> CreateTarget()
{ {
Image<TPixel> source = this.Source; Image<TPixel> source = this.Source;
Size targetSize = this.GetTargetSize(); Size destinationSize = this.GetDestinationSize();
// We will always be creating the clone even for mutate because we may need to resize the canvas. // We will always be creating the clone even for mutate because we may need to resize the canvas.
var targetFrames = new ImageFrame<TPixel>[source.Frames.Count]; var destinationFrames = new ImageFrame<TPixel>[source.Frames.Count];
for (int i = 0; i < targetFrames.Length; i++) for (int i = 0; i < destinationFrames.Length; i++)
{ {
targetFrames[i] = new ImageFrame<TPixel>( destinationFrames[i] = new ImageFrame<TPixel>(
this.Configuration, this.Configuration,
targetSize.Width, destinationSize.Width,
targetSize.Height, destinationSize.Height,
source.Frames[i].Metadata.DeepClone()); source.Frames[i].Metadata.DeepClone());
} }
// Use the overload to prevent an extra frame being added. // Use the overload to prevent an extra frame being added.
return new Image<TPixel>(this.Configuration, source.Metadata.DeepClone(), targetFrames); return new Image<TPixel>(this.Configuration, source.Metadata.DeepClone(), destinationFrames);
} }
private void CheckFrameCount(Image<TPixel> a, Image<TPixel> b) private void CheckFrameCount(Image<TPixel> a, Image<TPixel> b)

188
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs

@ -1,188 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Provides the base methods to perform affine transforms on an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Size targetSize;
private readonly Matrix3x2 transformMatrix;
private readonly IResampler resampler;
/// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="AffineTransformProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.targetSize = definition.TargetDimensions;
this.transformMatrix = definition.TransformMatrix;
this.resampler = definition.Sampler;
}
protected override Size GetTargetSize() => this.targetSize;
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
// Handle transforms that result in output identical to the original.
if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix3x2.Identity))
{
// The clone will be blank here copy all the pixel data over
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup());
return;
}
int width = this.targetSize.Width;
var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration;
// Convert from screen to world space.
Matrix3x2.Invert(this.transformMatrix, out Matrix3x2 matrix);
if (this.resampler is NearestNeighborResampler)
{
var nnOperation = new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination);
ParallelRowIterator.IterateRows(
configuration,
targetBounds,
in nnOperation);
return;
}
using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination);
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>(
configuration,
targetBounds,
in operation);
}
/// <summary>
/// A <see langword="struct"/> implementing the nearest neighbor resampler logic for <see cref="AffineTransformProcessor{T}"/>.
/// </summary>
private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly Matrix3x2 matrix;
private readonly int maxX;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public NearestNeighborRowIntervalOperation(
Rectangle bounds,
ref Matrix3x2 matrix,
int maxX,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination)
{
this.bounds = bounds;
this.matrix = matrix;
this.maxX = maxX;
this.source = source;
this.destination = destination;
}
/// <inheritdoc/>
/// <param name="rows"></param>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> destRow = this.destination.GetPixelRowSpan(y);
for (int x = 0; x < this.maxX; x++)
{
var point = Point.Transform(new Point(x, y), this.matrix);
if (this.bounds.Contains(point.X, point.Y))
{
destRow[x] = this.source[point.X, point.Y];
}
}
}
}
}
/// <summary>
/// A <see langword="struct"/> implementing the transformation logic for <see cref="AffineTransformProcessor{T}"/>.
/// </summary>
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4>
{
private readonly Configuration configuration;
private readonly TransformKernelMap kernelMap;
private readonly Matrix3x2 matrix;
private readonly int maxX;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Configuration configuration,
TransformKernelMap kernelMap,
ref Matrix3x2 matrix,
int maxX,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination)
{
this.configuration = configuration;
this.kernelMap = kernelMap;
this.matrix = matrix;
this.maxX = maxX;
this.source = source;
this.destination = destination;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRowSpan = this.destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan, span);
ref float ySpanRef = ref this.kernelMap.GetYStartReference(y);
ref float xSpanRef = ref this.kernelMap.GetXStartReference(y);
for (int x = 0; x < this.maxX; x++)
{
// Use the single precision position to calculate correct bounding pixels
// otherwise we get rogue pixels outside of the bounds.
var point = Vector2.Transform(new Vector2(x, y), this.matrix);
this.kernelMap.Convolve(
point,
x,
ref ySpanRef,
ref xSpanRef,
this.source.PixelBuffer,
span);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration,
span,
targetRowSpan);
}
}
}
}
}

2
src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
=> this.cropRectangle = definition.CropRectangle; => this.cropRectangle = definition.CropRectangle;
/// <inheritdoc/> /// <inheritdoc/>
protected override Size GetTargetSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height); protected override Size GetDestinationSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height);
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)

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

@ -1,6 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
@ -21,5 +23,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The <see cref="float"/> /// The <see cref="float"/>
/// </returns> /// </returns>
float GetValue(float x); float GetValue(float x);
/// <summary>
/// Applies a transformation upon an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="processor">The transforming image processor.</param>
void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor)
where TPixel : struct, IPixel<TPixel>;
} }
} }

23
src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs

@ -0,0 +1,23 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Implements an algorithm to alter the pixels of an image via resampling transforms.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IResamplingTransformImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Applies a resampling transform with the given sampler.
/// </summary>
/// <typeparam name="TResampler">The type of sampler.</typeparam>
/// <param name="sampler">The sampler to use.</param>
void ApplyTransform<TResampler>(in TResampler sampler)
where TResampler : struct, IResampler;
}
}

8
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs → src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs

@ -19,9 +19,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions)
{ {
Guard.NotNull(sampler, nameof(sampler)); Guard.NotNull(sampler, nameof(sampler));
Guard.MustBeValueType(sampler, nameof(sampler));
this.Sampler = sampler; this.Sampler = sampler;
this.TransformMatrix = matrix; this.TransformMatrix = matrix;
this.TargetDimensions = targetDimensions; this.DestinationSize = targetDimensions;
} }
/// <summary> /// <summary>
@ -35,9 +37,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Matrix3x2 TransformMatrix { get; } public Matrix3x2 TransformMatrix { get; }
/// <summary> /// <summary>
/// Gets the target dimensions to constrain the transformed image to. /// Gets the destination size to constrain the transformed image to.
/// </summary> /// </summary>
public Size TargetDimensions { get; } public Size DestinationSize { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)

234
src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs

@ -0,0 +1,234 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Provides the base methods to perform affine transforms on an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel>, IResamplingTransformImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly Size destinationSize;
private readonly Matrix3x2 transformMatrix;
private readonly IResampler resampler;
private ImageFrame<TPixel> source;
private ImageFrame<TPixel> destination;
/// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="AffineTransformProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
this.destinationSize = definition.DestinationSize;
this.transformMatrix = definition.TransformMatrix;
this.resampler = definition.Sampler;
}
protected override Size GetDestinationSize() => this.destinationSize;
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
this.source = source;
this.destination = destination;
this.resampler.ApplyTransform(this);
}
/// <inheritdoc/>
public void ApplyTransform<TResampler>(in TResampler sampler)
where TResampler : struct, IResampler
{
Configuration configuration = this.Configuration;
ImageFrame<TPixel> source = this.source;
ImageFrame<TPixel> destination = this.destination;
Matrix3x2 matrix = this.transformMatrix;
// Handle transforms that result in output identical to the original.
if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity))
{
// The clone will be blank here copy all the pixel data over
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup());
return;
}
// Convert from screen to world space.
Matrix3x2.Invert(matrix, out matrix);
if (sampler is NearestNeighborResampler)
{
var nnOperation = new NNAffineOperation(source, destination, matrix);
ParallelRowIterator.IterateRows(
configuration,
destination.Bounds(),
in nnOperation);
return;
}
int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height);
int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width);
var radialExtents = new Vector2(xRadius, yRadius);
int yLength = (yRadius * 2) + 1;
int xLength = (xRadius * 2) + 1;
// We use 2D buffers so that we can access the weight spans per row in parallel.
using Buffer2D<float> yKernelBuffer = configuration.MemoryAllocator.Allocate2D<float>(yLength, destination.Height);
using Buffer2D<float> xKernelBuffer = configuration.MemoryAllocator.Allocate2D<float>(xLength, destination.Height);
int maxX = source.Width - 1;
int maxY = source.Height - 1;
var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY);
var operation = new AffineOperation<TResampler>(
configuration,
source,
destination,
yKernelBuffer,
xKernelBuffer,
in sampler,
matrix,
radialExtents,
maxSourceExtents);
ParallelRowIterator.IterateRows<AffineOperation<TResampler>, Vector4>(
configuration,
destination.Bounds(),
in operation);
}
private readonly struct NNAffineOperation : IRowIntervalOperation
{
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
private readonly Rectangle bounds;
private readonly Matrix3x2 matrix;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)]
public NNAffineOperation(
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
{
this.source = source;
this.destination = destination;
this.bounds = source.Bounds();
this.matrix = matrix;
this.maxX = destination.Width;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> destRow = this.destination.GetPixelRowSpan(y);
for (int x = 0; x < this.maxX; x++)
{
var point = Vector2.Transform(new Vector2(x, y), this.matrix);
int px = (int)MathF.Round(point.X);
int py = (int)MathF.Round(point.Y);
if (this.bounds.Contains(px, py))
{
destRow[x] = this.source[px, py];
}
}
}
}
}
private readonly struct AffineOperation<TResampler> : IRowIntervalOperation<Vector4>
where TResampler : struct, IResampler
{
private readonly Configuration configuration;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
private readonly Buffer2D<float> yKernelBuffer;
private readonly Buffer2D<float> xKernelBuffer;
private readonly TResampler sampler;
private readonly Matrix3x2 matrix;
private readonly Vector2 radialExtents;
private readonly Vector4 maxSourceExtents;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)]
public AffineOperation(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Buffer2D<float> yKernelBuffer,
Buffer2D<float> xKernelBuffer,
in TResampler sampler,
Matrix3x2 matrix,
Vector2 radialExtents,
Vector4 maxSourceExtents)
{
this.configuration = configuration;
this.source = source;
this.destination = destination;
this.yKernelBuffer = yKernelBuffer;
this.xKernelBuffer = xKernelBuffer;
this.sampler = sampler;
this.matrix = matrix;
this.radialExtents = radialExtents;
this.maxSourceExtents = maxSourceExtents;
this.maxX = destination.Width;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span)
{
Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer;
for (int y = rows.Min; y < rows.Max; y++)
{
PixelOperations<TPixel>.Instance.ToVector4(
this.configuration,
this.destination.GetPixelRowSpan(y),
span);
ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y));
ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y));
for (int x = 0; x < this.maxX; x++)
{
// Use the single precision position to calculate correct bounding pixels
// otherwise we get rogue pixels outside of the bounds.
var point = Vector2.Transform(new Vector2(x, y), this.matrix);
LinearTransformUtilities.Convolve(
in this.sampler,
point,
sourceBuffer,
span,
x,
ref yKernelSpanRef,
ref xKernelSpanRef,
this.radialExtents,
this.maxSourceExtents);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration,
span,
this.destination.GetPixelRowSpan(y));
}
}
}
}
}

0
src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs → src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs

0
src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs → src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs

0
src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs → src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs

0
src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs → src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs

104
src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs

@ -0,0 +1,104 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Utility methods for affine and projective transforms.
/// </summary>
internal static class LinearTransformUtilities
{
[MethodImpl(InliningOptions.ShortMethod)]
internal static int GetSamplingRadius<TResampler>(in TResampler sampler, int sourceSize, int destinationSize)
where TResampler : struct, IResampler
{
double scale = sourceSize / destinationSize;
if (scale < 1)
{
scale = 1;
}
return (int)Math.Ceiling(scale * sampler.Radius);
}
[MethodImpl(InliningOptions.ShortMethod)]
internal static void Convolve<TResampler, TPixel>(
in TResampler sampler,
Vector2 transformedPoint,
Buffer2D<TPixel> sourcePixels,
Span<Vector4> targetRow,
int column,
ref float yKernelSpanRef,
ref float xKernelSpanRef,
Vector2 radialExtents,
Vector4 maxSourceExtents)
where TResampler : struct, IResampler
where TPixel : struct, IPixel<TPixel>
{
// Clamp sampling pixel radial extents to the source image edges
Vector2 minXY = transformedPoint - radialExtents;
Vector2 maxXY = transformedPoint + radialExtents;
// left, top, right, bottom
var sourceExtents = new Vector4(
MathF.Ceiling(minXY.X),
MathF.Ceiling(minXY.Y),
MathF.Floor(maxXY.X),
MathF.Floor(maxXY.Y));
sourceExtents = Vector4.Clamp(sourceExtents, Vector4.Zero, maxSourceExtents);
int left = (int)sourceExtents.X;
int top = (int)sourceExtents.Y;
int right = (int)sourceExtents.Z;
int bottom = (int)sourceExtents.W;
if (left == right || top == bottom)
{
return;
}
CalculateWeights(in sampler, top, bottom, transformedPoint.Y, ref yKernelSpanRef);
CalculateWeights(in sampler, left, right, transformedPoint.X, ref xKernelSpanRef);
Vector4 sum = Vector4.Zero;
for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++)
{
float yWeight = Unsafe.Add(ref yKernelSpanRef, kernelY);
for (int kernelX = 0, x = left; x <= right; x++, kernelX++)
{
float xWeight = Unsafe.Add(ref xKernelSpanRef, kernelX);
// Values are first premultiplied to prevent darkening of edge pixels.
var current = sourcePixels[x, y].ToVector4();
Vector4Utils.Premultiply(ref current);
sum += current * xWeight * yWeight;
}
}
// Reverse the premultiplication
Vector4Utils.UnPremultiply(ref sum);
targetRow[column] = sum;
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void CalculateWeights<TResampler>(in TResampler sampler, int min, int max, float point, ref float weightsRef)
where TResampler : struct, IResampler
{
float sum = 0;
for (int x = 0, i = min; i <= max; i++, x++)
{
float weight = sampler.GetValue(i - point);
sum += weight;
Unsafe.Add(ref weightsRef, x) = weight;
}
}
}
}

8
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs → src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs

@ -19,9 +19,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions) public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions)
{ {
Guard.NotNull(sampler, nameof(sampler)); Guard.NotNull(sampler, nameof(sampler));
Guard.MustBeValueType(sampler, nameof(sampler));
this.Sampler = sampler; this.Sampler = sampler;
this.TransformMatrix = matrix; this.TransformMatrix = matrix;
this.TargetDimensions = targetDimensions; this.DestinationSize = targetDimensions;
} }
/// <summary> /// <summary>
@ -35,9 +37,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Matrix4x4 TransformMatrix { get; } public Matrix4x4 TransformMatrix { get; }
/// <summary> /// <summary>
/// Gets the target dimensions to constrain the transformed image to. /// Gets the destination size to constrain the transformed image to.
/// </summary> /// </summary>
public Size TargetDimensions { get; } public Size DestinationSize { get; }
/// <inheritdoc /> /// <inheritdoc />
public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)

156
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs → src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -17,9 +18,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel> internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly Size targetSize; private readonly Size destinationSize;
private readonly IResampler resampler; private readonly IResampler resampler;
private readonly Matrix4x4 transformMatrix; private readonly Matrix4x4 transformMatrix;
private ImageFrame<TPixel> source;
private ImageFrame<TPixel> destination;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class.
@ -31,74 +34,102 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public ProjectiveTransformProcessor(Configuration configuration, ProjectiveTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public ProjectiveTransformProcessor(Configuration configuration, ProjectiveTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
this.targetSize = definition.TargetDimensions; this.destinationSize = definition.DestinationSize;
this.transformMatrix = definition.TransformMatrix; this.transformMatrix = definition.TransformMatrix;
this.resampler = definition.Sampler; this.resampler = definition.Sampler;
} }
protected override Size GetTargetSize() => this.targetSize; protected override Size GetDestinationSize() => this.destinationSize;
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{ {
this.source = source;
this.destination = destination;
this.resampler.ApplyTransform(this);
}
/// <inheritdoc/>
public void ApplyTransform<TResampler>(in TResampler sampler)
where TResampler : struct, IResampler
{
Configuration configuration = this.Configuration;
ImageFrame<TPixel> source = this.source;
ImageFrame<TPixel> destination = this.destination;
Matrix4x4 matrix = this.transformMatrix;
// Handle transforms that result in output identical to the original. // Handle transforms that result in output identical to the original.
if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix4x4.Identity)) if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity))
{ {
// The clone will be blank here copy all the pixel data over // The clone will be blank here copy all the pixel data over
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup());
return; return;
} }
int width = this.targetSize.Width;
var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration;
// Convert from screen to world space. // Convert from screen to world space.
Matrix4x4.Invert(this.transformMatrix, out Matrix4x4 matrix); Matrix4x4.Invert(matrix, out matrix);
if (this.resampler is NearestNeighborResampler) if (sampler is NearestNeighborResampler)
{ {
Rectangle sourceBounds = this.SourceRectangle; var nnOperation = new NNProjectiveOperation(source, destination, matrix);
var nnOperation = new NearestNeighborRowIntervalOperation(sourceBounds, ref matrix, width, source, destination);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
configuration, configuration,
targetBounds, destination.Bounds(),
in nnOperation); in nnOperation);
return; return;
} }
using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height);
int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width);
var radialExtents = new Vector2(xRadius, yRadius);
int yLength = (yRadius * 2) + 1;
int xLength = (xRadius * 2) + 1;
// We use 2D buffers so that we can access the weight spans per row in parallel.
using Buffer2D<float> yKernelBuffer = configuration.MemoryAllocator.Allocate2D<float>(yLength, destination.Height);
using Buffer2D<float> xKernelBuffer = configuration.MemoryAllocator.Allocate2D<float>(xLength, destination.Height);
var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination); int maxX = source.Width - 1;
ParallelRowIterator.IterateRows<RowIntervalOperation, Vector4>( int maxY = source.Height - 1;
var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY);
var operation = new ProjectiveOperation<TResampler>(
configuration,
source,
destination,
yKernelBuffer,
xKernelBuffer,
in sampler,
matrix,
radialExtents,
maxSourceExtents);
ParallelRowIterator.IterateRows<ProjectiveOperation<TResampler>, Vector4>(
configuration, configuration,
targetBounds, destination.Bounds(),
in operation); in operation);
} }
private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation private readonly struct NNProjectiveOperation : IRowIntervalOperation
{ {
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly Matrix4x4 matrix; private readonly Matrix4x4 matrix;
private readonly int maxX; private readonly int maxX;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public NearestNeighborRowIntervalOperation( public NNProjectiveOperation(
Rectangle bounds,
ref Matrix4x4 matrix,
int maxX,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
ImageFrame<TPixel> destination) ImageFrame<TPixel> destination,
Matrix4x4 matrix)
{ {
this.bounds = bounds;
this.matrix = matrix;
this.maxX = maxX;
this.source = source; this.source = source;
this.destination = destination; this.destination = destination;
this.bounds = source.Bounds();
this.matrix = matrix;
this.maxX = destination.Width;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -110,7 +141,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int x = 0; x < this.maxX; x++) for (int x = 0; x < this.maxX; x++)
{ {
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix);
int px = (int)MathF.Round(point.X); int px = (int)MathF.Round(point.X);
int py = (int)MathF.Round(point.Y); int py = (int)MathF.Round(point.Y);
@ -123,60 +154,79 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
} }
private readonly struct RowIntervalOperation : IRowIntervalOperation<Vector4> private readonly struct ProjectiveOperation<TResampler> : IRowIntervalOperation<Vector4>
where TResampler : struct, IResampler
{ {
private readonly Configuration configuration; private readonly Configuration configuration;
private readonly TransformKernelMap kernelMap;
private readonly Matrix4x4 matrix;
private readonly int maxX;
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination; private readonly ImageFrame<TPixel> destination;
private readonly Buffer2D<float> yKernelBuffer;
private readonly Buffer2D<float> xKernelBuffer;
private readonly TResampler sampler;
private readonly Matrix4x4 matrix;
private readonly Vector2 radialExtents;
private readonly Vector4 maxSourceExtents;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation( public ProjectiveOperation(
Configuration configuration, Configuration configuration,
TransformKernelMap kernelMap,
ref Matrix4x4 matrix,
int maxX,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
ImageFrame<TPixel> destination) ImageFrame<TPixel> destination,
Buffer2D<float> yKernelBuffer,
Buffer2D<float> xKernelBuffer,
in TResampler sampler,
Matrix4x4 matrix,
Vector2 radialExtents,
Vector4 maxSourceExtents)
{ {
this.configuration = configuration; this.configuration = configuration;
this.kernelMap = kernelMap;
this.matrix = matrix;
this.maxX = maxX;
this.source = source; this.source = source;
this.destination = destination; this.destination = destination;
this.yKernelBuffer = yKernelBuffer;
this.xKernelBuffer = xKernelBuffer;
this.sampler = sampler;
this.matrix = matrix;
this.radialExtents = radialExtents;
this.maxSourceExtents = maxSourceExtents;
this.maxX = destination.Width;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows, Span<Vector4> span) public void Invoke(in RowInterval rows, Span<Vector4> span)
{ {
Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> targetRowSpan = this.destination.GetPixelRowSpan(y); PixelOperations<TPixel>.Instance.ToVector4(
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan, span); this.configuration,
ref float ySpanRef = ref this.kernelMap.GetYStartReference(y); this.destination.GetPixelRowSpan(y),
ref float xSpanRef = ref this.kernelMap.GetXStartReference(y); span);
ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y));
ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y));
for (int x = 0; x < this.maxX; x++) for (int x = 0; x < this.maxX; x++)
{ {
// Use the single precision position to calculate correct bounding pixels // Use the single precision position to calculate correct bounding pixels
// otherwise we get rogue pixels outside of the bounds. // otherwise we get rogue pixels outside of the bounds.
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix);
this.kernelMap.Convolve( LinearTransformUtilities.Convolve(
in this.sampler,
point, point,
sourceBuffer,
span,
x, x,
ref ySpanRef, ref yKernelSpanRef,
ref xSpanRef, ref xKernelSpanRef,
this.source.PixelBuffer, this.radialExtents,
span); this.maxSourceExtents);
} }
PixelOperations<TPixel>.Instance.FromVector4Destructive( PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration, this.configuration,
span, span,
targetRowSpan); this.destination.GetPixelRowSpan(y));
} }
} }
} }

4
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs → src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs

@ -28,14 +28,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="sourceSize">The source image size</param> /// <param name="sourceSize">The source image size</param>
public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) public RotateProcessor(float degrees, IResampler sampler, Size sourceSize)
: this( : this(
TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize), TransformUtilities.CreateRotationMatrixDegrees(degrees, sourceSize),
sampler, sampler,
sourceSize) sourceSize)
=> this.Degrees = degrees; => this.Degrees = degrees;
// Helper constructor // Helper constructor
private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize) private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize)
: base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, rotationMatrix)) : base(rotationMatrix, sampler, TransformUtilities.GetTransformedSize(sourceSize, rotationMatrix))
{ {
} }

0
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs → src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs

4
src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs → src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="sourceSize">The source image size</param> /// <param name="sourceSize">The source image size</param>
public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize)
: this( : this(
TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize), TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize),
sampler, sampler,
sourceSize) sourceSize)
{ {
@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// Helper constructor: // Helper constructor:
private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize) private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize)
: base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, skewMatrix)) : base(skewMatrix, sampler, TransformUtilities.GetTransformedSize(sourceSize, skewMatrix))
{ {
} }

24
src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs

@ -1,6 +1,9 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
@ -8,12 +11,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <see href="https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm">Wikipedia</see> /// <see href="https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm">Wikipedia</see>
/// A commonly used algorithm within image processing that preserves sharpness better than triangle interpolation. /// A commonly used algorithm within image processing that preserves sharpness better than triangle interpolation.
/// </summary> /// </summary>
public class BicubicResampler : IResampler public readonly struct BicubicResampler : IResampler
{ {
/// <inheritdoc/> /// <inheritdoc/>
public float Radius => 2; public float Radius => 2;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x) public float GetValue(float x)
{ {
if (x < 0F) if (x < 0F)
@ -21,21 +25,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
x = -x; x = -x;
} }
float result = 0;
// Given the coefficient "a" as -0.5F. // Given the coefficient "a" as -0.5F.
if (x <= 1F) if (x <= 1F)
{ {
// Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (x * x)) + 1; // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (x * x)) + 1;
result = (((1.5F * x) - 2.5F) * x * x) + 1; return (((1.5F * x) - 2.5F) * x * x) + 1;
} }
else if (x < 2F) else if (x < 2F)
{ {
// Below simplified result = (a * (x * x * x)) - ((5F * a) * (x * x)) + ((8F * a) * x) - (4F * a); // Below simplified result = (a * (x * x * x)) - ((5F * a) * (x * x)) + ((8F * a) * x) - (4F * a);
result = (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; return (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2;
} }
return result; return 0;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor)
where TPixel : struct, IPixel<TPixel>
=> processor.ApplyTransform(in this);
} }
} }

16
src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs

@ -1,18 +1,22 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// The function implements the box algorithm. Similar to nearest neighbor when upscaling. /// The function implements the box algorithm. Similar to nearest neighbor when upscaling.
/// When downscaling the pixels will average, merging together. /// When downscaling the pixels will average, merging together.
/// </summary> /// </summary>
public class BoxResampler : IResampler public readonly struct BoxResampler : IResampler
{ {
/// <inheritdoc/> /// <inheritdoc/>
public float Radius => 0.5F; public float Radius => 0.5F;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x) public float GetValue(float x)
{ {
if (x > -0.5F && x <= 0.5F) if (x > -0.5F && x <= 0.5F)
@ -22,5 +26,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0; return 0;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor)
where TPixel : struct, IPixel<TPixel>
=> processor.ApplyTransform(in this);
} }
} }

26
src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs

@ -1,26 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function.
/// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large
/// scale image enlargements that a 'Lagrange' filter can produce.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
/// </summary>
public class CatmullRomResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
public float GetValue(float x)
{
const float B = 0;
const float C = 0.5F;
return ImageMaths.GetBcValue(x, B, C);
}
}
}

112
src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs

@ -0,0 +1,112 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Cubic filters contain a collection of different filters of varying B-Spline and
/// Cardinal values. With these two values you can generate any smoothly fitting
/// (continuious first derivative) piece-wise cubic filter.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
/// <see href="https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf"/>
/// </summary>
public readonly struct CubicResampler : IResampler
{
private readonly float bspline;
private readonly float cardinal;
/// <summary>
/// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function.
/// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large
/// scale image enlargements that a 'Lagrange' filter can produce.
/// </summary>
public static CubicResampler CatmullRom = new CubicResampler(2, 0, .5F);
/// <summary>
/// The Hermite filter is type of smoothed triangular interpolation Filter,
/// This filter rounds off strong edges while preserving flat 'color levels' in the original image.
/// </summary>
public static CubicResampler Hermite = new CubicResampler(2, 0, 0);
/// <summary>
/// The function implements the Mitchell-Netravali algorithm as described on
/// <see href="https://de.wikipedia.org/wiki/Mitchell-Netravali-Filter">Wikipedia</see>
/// </summary>
public static CubicResampler MitchellNetravali = new CubicResampler(2, .3333333F, .3333333F);
/// <summary>
/// The function implements the Robidoux algorithm.
/// <see href="http://www.imagemagick.org/Usage/filter/#robidoux"/>
/// </summary>
public static CubicResampler Robidoux = new CubicResampler(2, .37821575509399867F, .31089212245300067F);
/// <summary>
/// The function implements the Robidoux Sharp algorithm.
/// <see href="http://www.imagemagick.org/Usage/filter/#robidoux"/>
/// </summary>
public static CubicResampler RobidouxSharp = new CubicResampler(2, .2620145123990142F, .3689927438004929F);
/// <summary>
/// The function implements the spline algorithm.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
/// </summary>
/// <summary>
/// The function implements the Robidoux Sharp algorithm.
/// <see href="http://www.imagemagick.org/Usage/filter/#robidoux"/>
/// </summary>
public static CubicResampler Spline = new CubicResampler(2, 1, 0);
/// <summary>
/// Initializes a new instance of the <see cref="CubicResampler"/> struct.
/// </summary>
/// <param name="radius">The sampling radius.</param>
/// <param name="bspline">The B-Spline value.</param>
/// <param name="cardinal">The Cardinal cubic value.</param>
public CubicResampler(float radius, float bspline, float cardinal)
{
this.Radius = radius;
this.bspline = bspline;
this.cardinal = cardinal;
}
/// <inheritdoc/>
public float Radius { get; }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
float b = this.bspline;
float c = this.cardinal;
if (x < 0F)
{
x = -x;
}
float temp = x * x;
if (x < 1F)
{
x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b));
return x / 6F;
}
if (x < 2F)
{
x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c));
return x / 6F;
}
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor)
where TPixel : struct, IPixel<TPixel>
=> processor.ApplyTransform(in this);
}
}

25
src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs

@ -1,25 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The Hermite filter is type of smoothed triangular interpolation Filter,
/// This filter rounds off strong edges while preserving flat 'color levels' in the original image.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
/// </summary>
public class HermiteResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
public float GetValue(float x)
{
const float B = 0F;
const float C = 0F;
return ImageMaths.GetBcValue(x, B, C);
}
}
}

32
src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs

@ -1,32 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The function implements the Lanczos kernel algorithm as described on
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
/// with a radius of 2 pixels.
/// </summary>
public class Lanczos2Resampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
public float GetValue(float x)
{
if (x < 0F)
{
x = -x;
}
if (x < 2F)
{
return ImageMaths.SinC(x) * ImageMaths.SinC(x / 2F);
}
return 0F;
}
}
}

32
src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs

@ -1,32 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The function implements the Lanczos kernel algorithm as described on
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
/// with a radius of 3 pixels.
/// </summary>
public class Lanczos3Resampler : IResampler
{
/// <inheritdoc/>
public float Radius => 3;
/// <inheritdoc/>
public float GetValue(float x)
{
if (x < 0F)
{
x = -x;
}
if (x < 3F)
{
return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3F);
}
return 0F;
}
}
}

32
src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs

@ -1,32 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The function implements the Lanczos kernel algorithm as described on
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
/// with a radius of 5 pixels.
/// </summary>
public class Lanczos5Resampler : IResampler
{
/// <inheritdoc/>
public float Radius => 5;
/// <inheritdoc/>
public float GetValue(float x)
{
if (x < 0F)
{
x = -x;
}
if (x < 5F)
{
return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5F);
}
return 0F;
}
}
}

32
src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs

@ -1,32 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The function implements the Lanczos kernel algorithm as described on
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
/// with a radius of 8 pixels.
/// </summary>
public class Lanczos8Resampler : IResampler
{
/// <inheritdoc/>
public float Radius => 8;
/// <inheritdoc/>
public float GetValue(float x)
{
if (x < 0F)
{
x = -x;
}
if (x < 8F)
{
return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8F);
}
return 0F;
}
}
}

68
src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs

@ -0,0 +1,68 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The function implements the Lanczos kernel algorithm as described on
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>.
/// </summary>
public readonly struct LanczosResampler : IResampler
{
/// <summary>
/// Implements the Lanczos kernel algorithm with a radius of 2.
/// </summary>
public static LanczosResampler Lanczos2 = new LanczosResampler(2);
/// <summary>
/// Implements the Lanczos kernel algorithm with a radius of 3.
/// </summary>
public static LanczosResampler Lanczos3 = new LanczosResampler(3);
/// <summary>
/// Implements the Lanczos kernel algorithm with a radius of 5.
/// </summary>
public static LanczosResampler Lanczos5 = new LanczosResampler(5);
/// <summary>
/// Implements the Lanczos kernel algorithm with a radius of 8.
/// </summary>
public static LanczosResampler Lanczos8 = new LanczosResampler(8);
/// <summary>
/// Initializes a new instance of the <see cref="LanczosResampler"/> struct.
/// </summary>
/// <param name="radius">The sampling radius.</param>
public LanczosResampler(float radius) => this.Radius = radius;
/// <inheritdoc/>
public float Radius { get; }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x < 0F)
{
x = -x;
}
float radius = this.Radius;
if (x < radius)
{
return ImageMaths.SinC(x) * ImageMaths.SinC(x / radius);
}
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor)
where TPixel : struct, IPixel<TPixel>
=> processor.ApplyTransform(in this);
}
}

24
src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The function implements the mitchell algorithm as described on
/// <see href="https://de.wikipedia.org/wiki/Mitchell-Netravali-Filter">Wikipedia</see>
/// </summary>
public class MitchellNetravaliResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
public float GetValue(float x)
{
const float B = 0.3333333F;
const float C = 0.3333333F;
return ImageMaths.GetBcValue(x, B, C);
}
}
}

21
src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs

@ -1,21 +1,28 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// The function implements the nearest neighbor algorithm. This uses an unscaled filter /// The function implements the nearest neighbor algorithm. This uses an unscaled filter
/// which will select the closest pixel to the new pixels position. /// which will select the closest pixel to the new pixels position.
/// </summary> /// </summary>
public class NearestNeighborResampler : IResampler public readonly struct NearestNeighborResampler : IResampler
{ {
/// <inheritdoc/> /// <inheritdoc/>
public float Radius => 1; public float Radius => 1;
/// <inheritdoc/> /// <inheritdoc/>
public float GetValue(float x) [MethodImpl(InliningOptions.ShortMethod)]
{ public float GetValue(float x) => x;
return x;
} /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor)
where TPixel : struct, IPixel<TPixel>
=> processor.ApplyTransform(in this);
} }
} }

24
src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The function implements the Robidoux algorithm.
/// <see href="http://www.imagemagick.org/Usage/filter/#robidoux"/>
/// </summary>
public class RobidouxResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
public float GetValue(float x)
{
const float B = 0.37821575509399867F;
const float C = 0.31089212245300067F;
return ImageMaths.GetBcValue(x, B, C);
}
}
}

24
src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The function implements the Robidoux Sharp algorithm.
/// <see href="http://www.imagemagick.org/Usage/filter/#robidoux"/>
/// </summary>
public class RobidouxSharpResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
public float GetValue(float x)
{
const float B = 0.2620145123990142F;
const float C = 0.3689927438004929F;
return ImageMaths.GetBcValue(x, B, C);
}
}
}

24
src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs

@ -1,24 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// The function implements the spline algorithm.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
/// </summary>
public class SplineResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
public float GetValue(float x)
{
const float B = 1F;
const float C = 0F;
return ImageMaths.GetBcValue(x, B, C);
}
}
}

16
src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs

@ -1,6 +1,9 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
@ -8,12 +11,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, /// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible,
/// so that one can calculate and assign appropriate intensity values to pixels. /// so that one can calculate and assign appropriate intensity values to pixels.
/// </summary> /// </summary>
public class TriangleResampler : IResampler public readonly struct TriangleResampler : IResampler
{ {
/// <inheritdoc/> /// <inheritdoc/>
public float Radius => 1; public float Radius => 1;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x) public float GetValue(float x)
{ {
if (x < 0F) if (x < 0F)
@ -28,5 +32,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F; return 0F;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor)
where TPixel : struct, IPixel<TPixel>
=> processor.ApplyTransform(in this);
} }
} }

16
src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs

@ -1,18 +1,22 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// The function implements the welch algorithm. /// The function implements the welch algorithm.
/// <see href="http://www.imagemagick.org/Usage/filter/"/> /// <see href="http://www.imagemagick.org/Usage/filter/"/>
/// </summary> /// </summary>
public class WelchResampler : IResampler public readonly struct WelchResampler : IResampler
{ {
/// <inheritdoc/> /// <inheritdoc/>
public float Radius => 3; public float Radius => 3;
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x) public float GetValue(float x)
{ {
if (x < 0F) if (x < 0F)
@ -27,5 +31,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F; return 0F;
} }
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyTransform<TPixel>(IResamplingTransformImageProcessor<TPixel> processor)
where TPixel : struct, IPixel<TPixel>
=> processor.ApplyTransform(in this);
} }
} }

17
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -28,12 +28,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Gets the start index for the destination row. /// Gets the start index for the destination row.
/// </summary> /// </summary>
public int StartIndex { get; } public int StartIndex
{
[MethodImpl(InliningOptions.ShortMethod)]
get;
}
/// <summary> /// <summary>
/// Gets the the length of the kernel. /// Gets the the length of the kernel.
/// </summary> /// </summary>
public int Length { get; } public int Length
{
[MethodImpl(InliningOptions.ShortMethod)]
get;
}
/// <summary> /// <summary>
/// Gets the span representing the portion of the <see cref="ResizeKernelMap"/> that this window covers. /// Gets the span representing the portion of the <see cref="ResizeKernelMap"/> that this window covers.
@ -81,6 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Copy the contents of <see cref="ResizeKernel"/> altering <see cref="StartIndex"/> /// Copy the contents of <see cref="ResizeKernel"/> altering <see cref="StartIndex"/>
/// to the value <paramref name="left"/>. /// to the value <paramref name="left"/>.
/// </summary> /// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal ResizeKernel AlterLeftValue(int left) internal ResizeKernel AlterLeftValue(int left)
{ {
return new ResizeKernel(left, this.bufferPtr, this.Length); return new ResizeKernel(left, this.bufferPtr, this.Length);
@ -96,4 +105,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
} }
} }
} }

17
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs

@ -1,13 +1,10 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <content>
/// Contains <see cref="PeriodicKernelMap"/>
/// </content>
internal partial class ResizeKernelMap internal partial class ResizeKernelMap
{ {
/// <summary> /// <summary>
@ -21,7 +18,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public PeriodicKernelMap( public PeriodicKernelMap(
MemoryAllocator memoryAllocator, MemoryAllocator memoryAllocator,
IResampler sampler,
int sourceLength, int sourceLength,
int destinationLength, int destinationLength,
double ratio, double ratio,
@ -31,7 +27,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int cornerInterval) int cornerInterval)
: base( : base(
memoryAllocator, memoryAllocator,
sampler,
sourceLength, sourceLength,
destinationLength, destinationLength,
(cornerInterval * 2) + period, (cornerInterval * 2) + period,
@ -45,15 +40,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}";
protected override void Initialize() protected internal override void Initialize<TResampler>(in TResampler sampler)
{ {
// Build top corner data + one period of the mosaic data: // Build top corner data + one period of the mosaic data:
int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; int startOfFirstRepeatedMosaic = this.cornerInterval + this.period;
for (int i = 0; i < startOfFirstRepeatedMosaic; i++) for (int i = 0; i < startOfFirstRepeatedMosaic; i++)
{ {
ResizeKernel kernel = this.BuildKernel(i, i); this.kernels[i] = this.BuildKernel(in sampler, i, i);
this.kernels[i] = kernel;
} }
// Copy the mosaics: // Copy the mosaics:
@ -70,10 +64,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int bottomStartData = this.cornerInterval + this.period; int bottomStartData = this.cornerInterval + this.period;
for (int i = 0; i < this.cornerInterval; i++) for (int i = 0; i < this.cornerInterval; i++)
{ {
ResizeKernel kernel = this.BuildKernel(bottomStartDest + i, bottomStartData + i); this.kernels[bottomStartDest + i] = this.BuildKernel(in sampler, bottomStartDest + i, bottomStartData + i);
this.kernels[bottomStartDest + i] = kernel;
} }
} }
} }
} }
} }

55
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -5,21 +5,17 @@ using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// Provides <see cref="ResizeKernel"/> values from an optimized, /// Provides resize kernel values from an optimized contiguous memory region.
/// contiguous memory region.
/// </summary> /// </summary>
internal partial class ResizeKernelMap : IDisposable internal partial class ResizeKernelMap : IDisposable
{ {
private static readonly TolerantMath TolerantMath = TolerantMath.Default; private static readonly TolerantMath TolerantMath = TolerantMath.Default;
private readonly IResampler sampler;
private readonly int sourceLength; private readonly int sourceLength;
private readonly double ratio; private readonly double ratio;
@ -34,12 +30,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ResizeKernel[] kernels; private readonly ResizeKernel[] kernels;
private bool isDisposed;
// To avoid both GC allocations, and MemoryAllocator ceremony: // To avoid both GC allocations, and MemoryAllocator ceremony:
private readonly double[] tempValues; private readonly double[] tempValues;
private ResizeKernelMap( private ResizeKernelMap(
MemoryAllocator memoryAllocator, MemoryAllocator memoryAllocator,
IResampler sampler,
int sourceLength, int sourceLength,
int destinationLength, int destinationLength,
int bufferHeight, int bufferHeight,
@ -47,7 +44,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
double scale, double scale,
int radius) int radius)
{ {
this.sampler = sampler;
this.ratio = ratio; this.ratio = ratio;
this.scale = scale; this.scale = scale;
this.radius = radius; this.radius = radius;
@ -80,30 +76,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Disposes <see cref="ResizeKernelMap"/> instance releasing it's backing buffer. /// Disposes <see cref="ResizeKernelMap"/> instance releasing it's backing buffer.
/// </summary> /// </summary>
public void Dispose() public void Dispose()
=> this.Dispose(true);
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing)
{ {
this.pinHandle.Dispose(); if (!this.isDisposed)
this.data.Dispose(); {
this.isDisposed = true;
if (disposing)
{
this.pinHandle.Dispose();
this.data.Dispose();
}
}
} }
/// <summary> /// <summary>
/// Returns a <see cref="ResizeKernel"/> for an index value between 0 and DestinationSize - 1. /// Returns a <see cref="ResizeKernel"/> for an index value between 0 and DestinationSize - 1.
/// </summary> /// </summary>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx];
/// <summary> /// <summary>
/// Computes the weights to apply at each pixel when resizing. /// Computes the weights to apply at each pixel when resizing.
/// </summary> /// </summary>
/// <typeparam name="TResampler">The type of sampler.</typeparam>
/// <param name="sampler">The <see cref="IResampler"/></param> /// <param name="sampler">The <see cref="IResampler"/></param>
/// <param name="destinationSize">The destination size</param> /// <param name="destinationSize">The destination size</param>
/// <param name="sourceSize">The source size</param> /// <param name="sourceSize">The source size</param>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations</param> /// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations</param>
/// <returns>The <see cref="ResizeKernelMap"/></returns> /// <returns>The <see cref="ResizeKernelMap"/></returns>
public static ResizeKernelMap Calculate( public static ResizeKernelMap Calculate<TResampler>(
IResampler sampler, in TResampler sampler,
int destinationSize, int destinationSize,
int sourceSize, int sourceSize,
MemoryAllocator memoryAllocator) MemoryAllocator memoryAllocator)
where TResampler : struct, IResampler
{ {
double ratio = (double)sourceSize / destinationSize; double ratio = (double)sourceSize / destinationSize;
double scale = ratio; double scale = ratio;
@ -144,7 +157,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ResizeKernelMap result = hasAtLeast2Periods ResizeKernelMap result = hasAtLeast2Periods
? new PeriodicKernelMap( ? new PeriodicKernelMap(
memoryAllocator, memoryAllocator,
sampler,
sourceSize, sourceSize,
destinationSize, destinationSize,
ratio, ratio,
@ -154,7 +166,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
cornerInterval) cornerInterval)
: new ResizeKernelMap( : new ResizeKernelMap(
memoryAllocator, memoryAllocator,
sampler,
sourceSize, sourceSize,
destinationSize, destinationSize,
destinationSize, destinationSize,
@ -162,17 +173,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
scale, scale,
radius); radius);
result.Initialize(); result.Initialize(in sampler);
return result; return result;
} }
protected virtual void Initialize() /// <summary>
/// Initializes the kernel map.
/// </summary>
protected internal virtual void Initialize<TResampler>(in TResampler sampler)
where TResampler : struct, IResampler
{ {
for (int i = 0; i < this.DestinationLength; i++) for (int i = 0; i < this.DestinationLength; i++)
{ {
ResizeKernel kernel = this.BuildKernel(i, i); this.kernels[i] = this.BuildKernel(in sampler, i, i);
this.kernels[i] = kernel;
} }
} }
@ -181,7 +195,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// referencing the data at row <paramref name="dataRowIndex"/> within <see cref="data"/>, /// referencing the data at row <paramref name="dataRowIndex"/> within <see cref="data"/>,
/// so the data reusable by other data rows. /// so the data reusable by other data rows.
/// </summary> /// </summary>
private ResizeKernel BuildKernel(int destRowIndex, int dataRowIndex) private ResizeKernel BuildKernel<TResampler>(in TResampler sampler, int destRowIndex, int dataRowIndex)
where TResampler : struct, IResampler
{ {
double center = ((destRowIndex + .5) * this.ratio) - .5; double center = ((destRowIndex + .5) * this.ratio) - .5;
@ -205,7 +220,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int j = left; j <= right; j++) for (int j = left; j <= right; j++)
{ {
double value = this.sampler.GetValue((float)((j - center) / this.scale)); double value = sampler.GetValue((float)((j - center) / this.scale));
sum += value; sum += value;
kernelValues[j - left] = value; kernelValues[j - left] = value;

17
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs

@ -17,13 +17,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(options.Sampler, nameof(options.Sampler)); Guard.NotNull(options.Sampler, nameof(options.Sampler));
Guard.MustBeValueType(options.Sampler, nameof(options.Sampler));
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options); (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options);
this.Sampler = options.Sampler; this.Sampler = options.Sampler;
this.TargetWidth = size.Width; this.DestinationWidth = size.Width;
this.TargetHeight = size.Height; this.DestinationHeight = size.Height;
this.TargetRectangle = rectangle; this.DestinationRectangle = rectangle;
this.Compand = options.Compand; this.Compand = options.Compand;
} }
@ -33,19 +34,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public IResampler Sampler { get; } public IResampler Sampler { get; }
/// <summary> /// <summary>
/// Gets the target width. /// Gets the destination width.
/// </summary> /// </summary>
public int TargetWidth { get; } public int DestinationWidth { get; }
/// <summary> /// <summary>
/// Gets the target height. /// Gets the destination height.
/// </summary> /// </summary>
public int TargetHeight { get; } public int DestinationHeight { get; }
/// <summary> /// <summary>
/// Gets the resize rectangle. /// Gets the resize rectangle.
/// </summary> /// </summary>
public Rectangle TargetRectangle { get; } public Rectangle DestinationRectangle { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether to compress or expand individual pixel color values on processing. /// Gets a value indicating whether to compress or expand individual pixel color values on processing.

201
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -12,59 +12,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Implements resizing of images using various resamplers. /// Implements resizing of images using various resamplers.
/// </summary> /// </summary>
/// <remarks>
/// The original code has been adapted from <see href="http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/filter_rcg.c"/>.
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel> internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private bool isDisposed; private readonly int destinationWidth;
private readonly int targetWidth; private readonly int destinationHeight;
private readonly int targetHeight;
private readonly IResampler resampler; private readonly IResampler resampler;
private readonly Rectangle targetRectangle; private readonly Rectangle destinationRectangle;
private readonly bool compand; private readonly bool compand;
private Image<TPixel> destination;
// The following fields are not immutable but are optionally created on demand.
private ResizeKernelMap horizontalKernelMap;
private ResizeKernelMap verticalKernelMap;
public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
this.targetWidth = definition.TargetWidth; this.destinationWidth = definition.DestinationWidth;
this.targetHeight = definition.TargetHeight; this.destinationHeight = definition.DestinationHeight;
this.targetRectangle = definition.TargetRectangle; this.destinationRectangle = definition.DestinationRectangle;
this.resampler = definition.Sampler; this.resampler = definition.Sampler;
this.compand = definition.Compand; this.compand = definition.Compand;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override Size GetTargetSize() => new Size(this.targetWidth, this.targetHeight); protected override Size GetDestinationSize() => new Size(this.destinationWidth, this.destinationHeight);
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeImageApply(Image<TPixel> destination) protected override void BeforeImageApply(Image<TPixel> destination)
{ {
if (!(this.resampler is NearestNeighborResampler)) this.destination = destination;
{ this.resampler.ApplyTransform(this);
Image<TPixel> source = this.Source;
Rectangle sourceRectangle = this.SourceRectangle;
// Since all image frame dimensions have to be the same we can calculate this for all frames.
MemoryAllocator memoryAllocator = source.GetMemoryAllocator();
this.horizontalKernelMap = ResizeKernelMap.Calculate(
this.resampler,
this.targetRectangle.Width,
sourceRectangle.Width,
memoryAllocator);
this.verticalKernelMap = ResizeKernelMap.Calculate(
this.resampler,
this.targetRectangle.Height,
sourceRectangle.Height,
memoryAllocator);
}
base.BeforeImageApply(destination); base.BeforeImageApply(destination);
} }
@ -72,54 +48,143 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{ {
Rectangle sourceRectangle = this.SourceRectangle; // Everything happens in BeforeImageApply.
}
public void ApplyTransform<TResampler>(in TResampler sampler)
where TResampler : struct, IResampler
{
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
Image<TPixel> source = this.Source;
Image<TPixel> destination = this.destination;
Rectangle sourceRectangle = this.SourceRectangle;
Rectangle destinationRectangle = this.destinationRectangle;
bool compand = this.compand;
// Handle resize dimensions identical to the original // Handle resize dimensions identical to the original
if (source.Width == destination.Width if (source.Width == destination.Width
&& source.Height == destination.Height && source.Height == destination.Height
&& sourceRectangle == this.targetRectangle) && sourceRectangle == destinationRectangle)
{ {
// The cloned will be blank here copy all the pixel data over for (int i = 0; i < source.Frames.Count; i++)
source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); {
ImageFrame<TPixel> sourceFrame = source.Frames[i];
ImageFrame<TPixel> destinationFrame = destination.Frames[i];
// The cloned will be blank here copy all the pixel data over
sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup());
}
return; return;
} }
int width = this.targetWidth; var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds());
int height = this.targetHeight;
var interest = Rectangle.Intersect(this.targetRectangle, new Rectangle(0, 0, width, height)); if (sampler is NearestNeighborResampler)
{
for (int i = 0; i < source.Frames.Count; i++)
{
ImageFrame<TPixel> sourceFrame = source.Frames[i];
ImageFrame<TPixel> destinationFrame = destination.Frames[i];
ApplyNNResizeFrameTransform(
configuration,
sourceFrame,
destinationFrame,
sourceRectangle,
destinationRectangle,
interest);
}
return;
}
if (this.resampler is NearestNeighborResampler) // Since all image frame dimensions have to be the same we can calculate
// the kernel maps and reuse for all frames.
MemoryAllocator allocator = configuration.MemoryAllocator;
using var horizontalKernelMap = ResizeKernelMap.Calculate(
in sampler,
destinationRectangle.Width,
sourceRectangle.Width,
allocator);
using var verticalKernelMap = ResizeKernelMap.Calculate(
in sampler,
destinationRectangle.Height,
sourceRectangle.Height,
allocator);
for (int i = 0; i < source.Frames.Count; i++)
{ {
// Scaling factors ImageFrame<TPixel> sourceFrame = source.Frames[i];
float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; ImageFrame<TPixel> destinationFrame = destination.Frames[i];
float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height;
var operation = new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination); ApplyResizeFrameTransform(
ParallelRowIterator.IterateRows(
configuration, configuration,
sourceFrame,
destinationFrame,
horizontalKernelMap,
verticalKernelMap,
sourceRectangle,
destinationRectangle,
interest, interest,
in operation); compand);
return;
} }
}
private static void ApplyNNResizeFrameTransform(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
Rectangle interest)
{
// Scaling factors
float widthFactor = sourceRectangle.Width / (float)destinationRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height;
var operation = new NNRowIntervalOperation(
sourceRectangle,
destinationRectangle,
widthFactor,
heightFactor,
source,
destination);
ParallelRowIterator.IterateRows(
configuration,
interest,
in operation);
}
private static void ApplyResizeFrameTransform(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
ResizeKernelMap horizontalKernelMap,
ResizeKernelMap verticalKernelMap,
Rectangle sourceRectangle,
Rectangle destinationRectangle,
Rectangle interest,
bool compand)
{
PixelConversionModifiers conversionModifiers = PixelConversionModifiers conversionModifiers =
PixelConversionModifiers.Premultiply.ApplyCompanding(this.compand); PixelConversionModifiers.Premultiply.ApplyCompanding(compand);
BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle); BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle);
// To reintroduce parallel processing, we to launch multiple workers // To reintroduce parallel processing, we would launch multiple workers
// for different row intervals of the image. // for different row intervals of the image.
using (var worker = new ResizeWorker<TPixel>( using (var worker = new ResizeWorker<TPixel>(
configuration, configuration,
sourceArea, sourceArea,
conversionModifiers, conversionModifiers,
this.horizontalKernelMap, horizontalKernelMap,
this.verticalKernelMap, verticalKernelMap,
width, destination.Width,
interest, interest,
this.targetRectangle.Location)) destinationRectangle.Location))
{ {
worker.Initialize(); worker.Initialize();
@ -128,27 +193,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
} }
/// <inheritdoc/> private readonly struct NNRowIntervalOperation : IRowIntervalOperation
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.horizontalKernelMap?.Dispose();
this.horizontalKernelMap = null;
this.verticalKernelMap?.Dispose();
this.verticalKernelMap = null;
}
this.isDisposed = true;
base.Dispose(disposing);
}
private readonly struct RowIntervalOperation : IRowIntervalOperation
{ {
private readonly Rectangle sourceBounds; private readonly Rectangle sourceBounds;
private readonly Rectangle destinationBounds; private readonly Rectangle destinationBounds;
@ -158,7 +203,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination; private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation( public NNRowIntervalOperation(
Rectangle sourceBounds, Rectangle sourceBounds,
Rectangle destinationBounds, Rectangle destinationBounds,
float widthFactor, float widthFactor,

3
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs

@ -6,7 +6,6 @@ using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -104,7 +103,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.tempColumnBuffer.Dispose(); this.tempColumnBuffer.Dispose();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(InliningOptions.ShortMethod)]
public Span<Vector4> GetColumnSpan(int x, int startY) public Span<Vector4> GetColumnSpan(int x, int startY)
{ {
return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min);

160
src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs

@ -1,160 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Contains the methods required to calculate transform kernel convolution.
/// </summary>
internal class TransformKernelMap : IDisposable
{
private readonly Buffer2D<float> yBuffer;
private readonly Buffer2D<float> xBuffer;
private readonly Vector2 extents;
private Vector4 maxSourceExtents;
private readonly IResampler sampler;
/// <summary>
/// Initializes a new instance of the <see cref="TransformKernelMap"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source size.</param>
/// <param name="destination">The destination size.</param>
/// <param name="sampler">The sampler.</param>
public TransformKernelMap(Configuration configuration, Size source, Size destination, IResampler sampler)
{
this.sampler = sampler;
float yRadius = this.GetSamplingRadius(source.Height, destination.Height);
float xRadius = this.GetSamplingRadius(source.Width, destination.Width);
this.extents = new Vector2(xRadius, yRadius);
int xLength = (int)MathF.Ceiling((this.extents.X * 2) + 2);
int yLength = (int)MathF.Ceiling((this.extents.Y * 2) + 2);
// We use 2D buffers so that we can access the weight spans per row in parallel.
this.yBuffer = configuration.MemoryAllocator.Allocate2D<float>(yLength, destination.Height);
this.xBuffer = configuration.MemoryAllocator.Allocate2D<float>(xLength, destination.Height);
int maxX = source.Width - 1;
int maxY = source.Height - 1;
this.maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY);
}
/// <summary>
/// Gets a reference to the first item of the y window.
/// </summary>
/// <returns>The reference to the first item of the window.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ref float GetYStartReference(int y)
=> ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpan(y));
/// <summary>
/// Gets a reference to the first item of the x window.
/// </summary>
/// <returns>The reference to the first item of the window.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public ref float GetXStartReference(int y)
=> ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpan(y));
public void Convolve<TPixel>(
Vector2 transformedPoint,
int column,
ref float ySpanRef,
ref float xSpanRef,
Buffer2D<TPixel> sourcePixels,
Span<Vector4> targetRow)
where TPixel : unmanaged, IPixel<TPixel>
{
// Clamp sampling pixel radial extents to the source image edges
Vector2 minXY = transformedPoint - this.extents;
Vector2 maxXY = transformedPoint + this.extents;
// left, top, right, bottom
var extents = new Vector4(
MathF.Ceiling(minXY.X - .5F),
MathF.Ceiling(minXY.Y - .5F),
MathF.Floor(maxXY.X + .5F),
MathF.Floor(maxXY.Y + .5F));
extents = Vector4.Clamp(extents, Vector4.Zero, this.maxSourceExtents);
int left = (int)extents.X;
int top = (int)extents.Y;
int right = (int)extents.Z;
int bottom = (int)extents.W;
if (left == right || top == bottom)
{
return;
}
this.CalculateWeights(top, bottom, transformedPoint.Y, ref ySpanRef);
this.CalculateWeights(left, right, transformedPoint.X, ref xSpanRef);
Vector4 sum = Vector4.Zero;
for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++)
{
float yWeight = Unsafe.Add(ref ySpanRef, kernelY);
for (int kernelX = 0, x = left; x <= right; x++, kernelX++)
{
float xWeight = Unsafe.Add(ref xSpanRef, kernelX);
// Values are first premultiplied to prevent darkening of edge pixels.
var current = sourcePixels[x, y].ToVector4();
Vector4Utils.Premultiply(ref current);
sum += current * xWeight * yWeight;
}
}
// Reverse the premultiplication
Vector4Utils.UnPremultiply(ref sum);
targetRow[column] = sum;
}
/// <summary>
/// Calculated the normalized weights for the given point.
/// </summary>
/// <param name="min">The minimum sampling offset</param>
/// <param name="max">The maximum sampling offset</param>
/// <param name="point">The transformed point dimension</param>
/// <param name="weightsRef">The reference to the collection of weights</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void CalculateWeights(int min, int max, float point, ref float weightsRef)
{
float sum = 0;
for (int x = 0, i = min; i <= max; i++, x++)
{
float weight = this.sampler.GetValue(i - point);
sum += weight;
Unsafe.Add(ref weightsRef, x) = weight;
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private float GetSamplingRadius(int sourceSize, int destinationSize)
{
float scale = (float)sourceSize / destinationSize;
if (scale < 1F)
{
scale = 1F;
}
return MathF.Ceiling(scale * this.sampler.Radius);
}
public void Dispose()
{
this.yBuffer?.Dispose();
this.xBuffer?.Dispose();
}
}
}

13
src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs → src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs

@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Contains utility methods for working with transforms. /// Contains utility methods for working with transforms.
/// </summary> /// </summary>
internal static class TransformUtils internal static class TransformUtilities
{ {
/// <summary> /// <summary>
/// Applies the projective transform against the given coordinates flattened into the 2D space. /// Applies the projective transform against the given coordinates flattened into the 2D space.
@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="degrees">The amount of rotation, in degrees.</param> /// <param name="degrees">The amount of rotation, in degrees.</param>
/// <param name="size">The source image size.</param> /// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns> /// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateRotationMatrixDegrees(float degrees, Size size) public static Matrix3x2 CreateRotationMatrixDegrees(float degrees, Size size)
=> CreateCenteredTransformMatrix( => CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size), new Rectangle(Point.Empty, size),
@ -44,6 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="radians">The amount of rotation, in radians.</param> /// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="size">The source image size.</param> /// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns> /// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size) public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size)
=> CreateCenteredTransformMatrix( => CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size), new Rectangle(Point.Empty, size),
@ -56,6 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="degreesY">The Y angle, in degrees.</param> /// <param name="degreesY">The Y angle, in degrees.</param>
/// <param name="size">The source image size.</param> /// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns> /// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size) public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size)
=> CreateCenteredTransformMatrix( => CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size), new Rectangle(Point.Empty, size),
@ -68,6 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="radiansY">The Y angle, in radians.</param> /// <param name="radiansY">The Y angle, in radians.</param>
/// <param name="size">The source image size.</param> /// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns> /// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateSkewMatrixRadians(float radiansX, float radiansY, Size size) public static Matrix3x2 CreateSkewMatrixRadians(float radiansX, float radiansY, Size size)
=> CreateCenteredTransformMatrix( => CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size), new Rectangle(Point.Empty, size),
@ -79,6 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="sourceRectangle">The source image bounds.</param> /// <param name="sourceRectangle">The source image bounds.</param>
/// <param name="matrix">The transformation matrix.</param> /// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Matrix3x2"/></returns> /// <returns>The <see cref="Matrix3x2"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix)
{ {
Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix);
@ -105,6 +110,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="corner">An enumeration that indicates on which corners to taper the rectangle.</param> /// <param name="corner">An enumeration that indicates on which corners to taper the rectangle.</param>
/// <param name="fraction">The amount to taper.</param> /// <param name="fraction">The amount to taper.</param>
/// <returns>The <see cref="Matrix4x4"/></returns> /// <returns>The <see cref="Matrix4x4"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction) public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction)
{ {
Matrix4x4 matrix = Matrix4x4.Identity; Matrix4x4 matrix = Matrix4x4.Identity;
@ -225,6 +231,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <returns> /// <returns>
/// The <see cref="Rectangle"/>. /// The <see cref="Rectangle"/>.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix)
{ {
Rectangle transformed = GetTransformedRectangle(rectangle, matrix); Rectangle transformed = GetTransformedRectangle(rectangle, matrix);
@ -284,6 +291,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <returns> /// <returns>
/// The <see cref="Rectangle"/>. /// The <see cref="Rectangle"/>.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix)
{ {
if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix))
@ -307,6 +315,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <returns> /// <returns>
/// The <see cref="Size"/>. /// The <see cref="Size"/>.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Size GetTransformedSize(Size size, Matrix4x4 matrix) public static Size GetTransformedSize(Size size, Matrix4x4 matrix)
{ {
Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!");
@ -321,6 +330,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ConstrainSize(rectangle); return ConstrainSize(rectangle);
} }
[MethodImpl(InliningOptions.ShortMethod)]
private static Size ConstrainSize(Rectangle rectangle) private static Size ConstrainSize(Rectangle rectangle)
{ {
// We want to resize the canvas here taking into account any translations. // We want to resize the canvas here taking into account any translations.
@ -342,6 +352,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return new Size(width, height); return new Size(width, height);
} }
[MethodImpl(InliningOptions.ShortMethod)]
private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br)
{ {
// Find the minimum and maximum "corners" based on the given vectors // Find the minimum and maximum "corners" based on the given vectors

12
src/ImageSharp/Processing/ProjectiveTransformBuilder.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="fraction">The amount to taper.</param> /// <param name="fraction">The amount to taper.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns> /// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction)
=> this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); => this.Prepend(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction));
/// <summary> /// <summary>
/// Appends a matrix that performs a tapering projective transform. /// Appends a matrix that performs a tapering projective transform.
@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="fraction">The amount to taper.</param> /// <param name="fraction">The amount to taper.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns> /// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction)
=> this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); => this.Append(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction));
/// <summary> /// <summary>
/// Prepends a centered rotation matrix using the given rotation in degrees. /// Prepends a centered rotation matrix using the given rotation in degrees.
@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radians">The amount of rotation, in radians.</param> /// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns> /// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependRotationRadians(float radians) public ProjectiveTransformBuilder PrependRotationRadians(float radians)
=> this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size)));
/// <summary> /// <summary>
/// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin.
@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radians">The amount of rotation, in radians.</param> /// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns> /// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendRotationRadians(float radians) public ProjectiveTransformBuilder AppendRotationRadians(float radians)
=> this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); => this.Append(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size)));
/// <summary> /// <summary>
/// Appends a centered rotation matrix using the given rotation in degrees at the given origin. /// Appends a centered rotation matrix using the given rotation in degrees at the given origin.
@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radiansY">The Y angle, in radians.</param> /// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns> /// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY)
=> this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size)));
/// <summary> /// <summary>
/// Prepends a skew matrix using the given angles in degrees at the given origin. /// Prepends a skew matrix using the given angles in degrees at the given origin.
@ -205,7 +205,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radiansY">The Y angle, in radians.</param> /// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns> /// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY)
=> this.Append(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); => this.Append(size => new Matrix4x4(TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size)));
/// <summary> /// <summary>
/// Appends a skew matrix using the given angles in degrees at the given origin. /// Appends a skew matrix using the given angles in degrees at the given origin.

26
tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs

@ -34,32 +34,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
} }
} }
// #### 25th October 2019 ####
//
// BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
// .NET Core SDK = 3.0.100
//
// [Host] : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT
// Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.8.4018.0
// Core : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT
//
// IterationCount=3 LaunchCount=1 WarmupCount=3
//
// #### Before ####
//
// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------- |----- |-------- |----------:|---------:|---------:|------:|------:|------:|----------:|
// | DoDiffuse | Clr | Clr | 129.58 ms | 24.60 ms | 1.349 ms | - | - | - | 6 KB |
// | DoDiffuse | Core | Core | 92.63 ms | 89.78 ms | 4.921 ms | - | - | - | 4.58 KB |
//
// #### After ####
//
// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
// |---------- |----- |-------- |----------:|----------:|----------:|------:|------:|------:|----------:|
// | DoDiffuse | Clr | Clr | 124.93 ms | 33.297 ms | 1.8251 ms | - | - | - | 2 KB |
// | DoDiffuse | Core | Core | 89.63 ms | 9.895 ms | 0.5424 ms | - | - | - | 1.91 KB |
// #### 20th February 2020 #### // #### 20th February 2020 ####
// //
// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 // BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363

42
tests/ImageSharp.Benchmarks/Samplers/Rotate.cs

@ -23,27 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
} }
} }
/* // #### 21th February 2020 ####
Nov 7 2018 //
BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763 // BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores // Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK = 2.1.403 // .NET Core SDK = 3.1.101
//
[Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT // [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 // Job-HOGSNT : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT
Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT // Job-FKDHXC : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT
// Job-ODABAZ : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
LaunchCount=1 TargetCount=3 WarmupCount=3 //
// IterationCount=3 LaunchCount=1 WarmupCount=3
#### BEFORE ####: //
Method | Runtime | Mean | Error | StdDev | Allocated | // | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
--------- |-------- |---------:|----------:|----------:|----------:| // |--------- |-------------- |---------:|---------:|---------:|------:|------:|------:|----------:|
DoRotate | Clr | 85.19 ms | 13.379 ms | 0.7560 ms | 6 KB | // | DoRotate | .NET 4.7.2 | 28.77 ms | 3.304 ms | 0.181 ms | - | - | - | 6.5 KB |
DoRotate | Core | 53.51 ms | 9.512 ms | 0.5375 ms | 4.29 KB | // | DoRotate | .NET Core 2.1 | 16.27 ms | 1.044 ms | 0.057 ms | - | - | - | 5.25 KB |
// | DoRotate | .NET Core 3.1 | 17.12 ms | 4.352 ms | 0.239 ms | - | - | - | 6.57 KB |
#### AFTER ####:
Method | Runtime | Mean | Error | StdDev | Allocated |
--------- |-------- |---------:|---------:|---------:|----------:|
DoRotate | Clr | 77.08 ms | 23.97 ms | 1.354 ms | 6 KB |
DoRotate | Core | 40.36 ms | 47.43 ms | 2.680 ms | 4.36 KB |
*/

43
tests/ImageSharp.Benchmarks/Samplers/Skew.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -24,27 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
} }
} }
/* // #### 21th February 2020 ####
Nov 7 2018 //
BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763 // BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores // Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK = 2.1.403 // .NET Core SDK = 3.1.101
//
[Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT // [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 // Job-VKKTMF : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT
Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT // Job-KTVRKR : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT
// Job-EONWDB : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
LaunchCount=1 TargetCount=3 WarmupCount=3 //
// IterationCount=3 LaunchCount=1 WarmupCount=3
#### BEFORE ####: //
Method | Runtime | Mean | Error | StdDev | Allocated | // | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
------- |-------- |---------:|---------:|----------:|----------:| // |------- |-------------- |---------:|----------:|---------:|------:|------:|------:|----------:|
DoSkew | Clr | 78.14 ms | 8.383 ms | 0.4736 ms | 6 KB | // | DoSkew | .NET 4.7.2 | 24.60 ms | 33.971 ms | 1.862 ms | - | - | - | 6.5 KB |
DoSkew | Core | 44.22 ms | 4.109 ms | 0.2322 ms | 4.28 KB | // | DoSkew | .NET Core 2.1 | 12.13 ms | 2.256 ms | 0.124 ms | - | - | - | 5.21 KB |
// | DoSkew | .NET Core 3.1 | 12.83 ms | 1.442 ms | 0.079 ms | - | - | - | 6.57 KB |
#### AFTER ####:
Method | Runtime | Mean | Error | StdDev | Allocated |
------- |-------- |---------:|----------:|----------:|----------:|
DoSkew | Clr | 71.63 ms | 25.589 ms | 1.4458 ms | 6 KB |
DoSkew | Core | 38.99 ms | 8.640 ms | 0.4882 ms | 4.36 KB |
*/

3
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs

@ -26,7 +26,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex];
public static ReferenceKernelMap Calculate(IResampler sampler, int destinationSize, int sourceSize, bool normalize = true) public static ReferenceKernelMap Calculate<TResampler>(in TResampler sampler, int destinationSize, int sourceSize, bool normalize = true)
where TResampler : struct, IResampler
{ {
double ratio = (double)sourceSize / destinationSize; double ratio = (double)sourceSize / destinationSize;
double scale = ratio; double scale = ratio;

122
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs

@ -25,59 +25,60 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
/// <summary> /// <summary>
/// resamplerName, srcSize, destSize /// resamplerName, srcSize, destSize
/// </summary> /// </summary>
public static readonly TheoryData<string, int, int> KernelMapData = new TheoryData<string, int, int> public static readonly TheoryData<IResampler, int, int> KernelMapData
= new TheoryData<IResampler, int, int>
{ {
{ nameof(KnownResamplers.Bicubic), 15, 10 }, { KnownResamplers.Bicubic, 15, 10 },
{ nameof(KnownResamplers.Bicubic), 10, 15 }, { KnownResamplers.Bicubic, 10, 15 },
{ nameof(KnownResamplers.Bicubic), 20, 20 }, { KnownResamplers.Bicubic, 20, 20 },
{ nameof(KnownResamplers.Bicubic), 50, 40 }, { KnownResamplers.Bicubic, 50, 40 },
{ nameof(KnownResamplers.Bicubic), 40, 50 }, { KnownResamplers.Bicubic, 40, 50 },
{ nameof(KnownResamplers.Bicubic), 500, 200 }, { KnownResamplers.Bicubic, 500, 200 },
{ nameof(KnownResamplers.Bicubic), 200, 500 }, { KnownResamplers.Bicubic, 200, 500 },
{ nameof(KnownResamplers.Bicubic), 3032, 400 }, { KnownResamplers.Bicubic, 3032, 400 },
{ nameof(KnownResamplers.Bicubic), 10, 25 }, { KnownResamplers.Bicubic, 10, 25 },
{ nameof(KnownResamplers.Lanczos3), 16, 12 }, { KnownResamplers.Lanczos3, 16, 12 },
{ nameof(KnownResamplers.Lanczos3), 12, 16 }, { KnownResamplers.Lanczos3, 12, 16 },
{ nameof(KnownResamplers.Lanczos3), 12, 9 }, { KnownResamplers.Lanczos3, 12, 9 },
{ nameof(KnownResamplers.Lanczos3), 9, 12 }, { KnownResamplers.Lanczos3, 9, 12 },
{ nameof(KnownResamplers.Lanczos3), 6, 8 }, { KnownResamplers.Lanczos3, 6, 8 },
{ nameof(KnownResamplers.Lanczos3), 8, 6 }, { KnownResamplers.Lanczos3, 8, 6 },
{ nameof(KnownResamplers.Lanczos3), 20, 12 }, { KnownResamplers.Lanczos3, 20, 12 },
{ nameof(KnownResamplers.Lanczos3), 5, 25 }, { KnownResamplers.Lanczos3, 5, 25 },
{ nameof(KnownResamplers.Lanczos3), 5, 50 }, { KnownResamplers.Lanczos3, 5, 50 },
{ nameof(KnownResamplers.Lanczos3), 25, 5 }, { KnownResamplers.Lanczos3, 25, 5 },
{ nameof(KnownResamplers.Lanczos3), 50, 5 }, { KnownResamplers.Lanczos3, 50, 5 },
{ nameof(KnownResamplers.Lanczos3), 49, 5 }, { KnownResamplers.Lanczos3, 49, 5 },
{ nameof(KnownResamplers.Lanczos3), 31, 5 }, { KnownResamplers.Lanczos3, 31, 5 },
{ nameof(KnownResamplers.Lanczos8), 500, 200 }, { KnownResamplers.Lanczos8, 500, 200 },
{ nameof(KnownResamplers.Lanczos8), 100, 10 }, { KnownResamplers.Lanczos8, 100, 10 },
{ nameof(KnownResamplers.Lanczos8), 100, 80 }, { KnownResamplers.Lanczos8, 100, 80 },
{ nameof(KnownResamplers.Lanczos8), 10, 100 }, { KnownResamplers.Lanczos8, 10, 100 },
// Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5: // Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5:
{ nameof(KnownResamplers.Box), 378, 149 }, { KnownResamplers.Box, 378, 149 },
{ nameof(KnownResamplers.Box), 349, 174 }, { KnownResamplers.Box, 349, 174 },
// Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData
{ nameof(KnownResamplers.Box), 201, 100 }, { KnownResamplers.Box, 201, 100 },
{ nameof(KnownResamplers.Box), 199, 99 }, { KnownResamplers.Box, 199, 99 },
{ nameof(KnownResamplers.Box), 10, 299 }, { KnownResamplers.Box, 10, 299 },
{ nameof(KnownResamplers.Box), 299, 10 }, { KnownResamplers.Box, 299, 10 },
{ nameof(KnownResamplers.Box), 301, 300 }, { KnownResamplers.Box, 301, 300 },
{ nameof(KnownResamplers.Box), 1180, 480 }, { KnownResamplers.Box, 1180, 480 },
{ nameof(KnownResamplers.Lanczos2), 3264, 3032 }, { KnownResamplers.Lanczos2, 3264, 3032 },
{ nameof(KnownResamplers.Bicubic), 1280, 2240 }, { KnownResamplers.Bicubic, 1280, 2240 },
{ nameof(KnownResamplers.Bicubic), 1920, 1680 }, { KnownResamplers.Bicubic, 1920, 1680 },
{ nameof(KnownResamplers.Bicubic), 3072, 2240 }, { KnownResamplers.Bicubic, 3072, 2240 },
{ nameof(KnownResamplers.Welch), 300, 2008 }, { KnownResamplers.Welch, 300, 2008 },
// ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData // ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData
{ nameof(KnownResamplers.Bicubic), 10, 50 }, { KnownResamplers.Bicubic, 10, 50 },
{ nameof(KnownResamplers.Bicubic), 49, 301 }, { KnownResamplers.Bicubic, 49, 301 },
{ nameof(KnownResamplers.Bicubic), 301, 49 }, { KnownResamplers.Bicubic, 301, 49 },
{ nameof(KnownResamplers.Bicubic), 1680, 1200 }, { KnownResamplers.Bicubic, 1680, 1200 },
{ nameof(KnownResamplers.Box), 13, 299 }, { KnownResamplers.Box, 13, 299 },
{ nameof(KnownResamplers.Lanczos5), 3032, 600 }, { KnownResamplers.Lanczos5, 3032, 600 },
}; };
public static TheoryData<string, int, int> GeneratedImageResizeData = public static TheoryData<string, int, int> GeneratedImageResizeData =
@ -85,20 +86,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[Theory(Skip = "Only for debugging and development")] [Theory(Skip = "Only for debugging and development")]
[MemberData(nameof(KernelMapData))] [MemberData(nameof(KernelMapData))]
public void PrintNonNormalizedKernelMap(string resamplerName, int srcSize, int destSize) public void PrintNonNormalizedKernelMap<TResampler>(TResampler resampler, int srcSize, int destSize)
where TResampler : struct, IResampler
{ {
IResampler resampler = TestUtils.GetResampler(resamplerName); var kernelMap = ReferenceKernelMap.Calculate<TResampler>(in resampler, destSize, srcSize, false);
var kernelMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize, false);
this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n");
} }
[Theory] [Theory]
[MemberData(nameof(KernelMapData))] [MemberData(nameof(KernelMapData))]
public void KernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) public void KernelMapContentIsCorrect<TResampler>(TResampler resampler, int srcSize, int destSize)
where TResampler : struct, IResampler
{ {
this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize); this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize);
} }
// Comprehensive but expensive tests, for ResizeKernelMap. // Comprehensive but expensive tests, for ResizeKernelMap.
@ -113,12 +114,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
} }
#endif #endif
private void VerifyKernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) private void VerifyKernelMapContentIsCorrect<TResampler>(TResampler resampler, int srcSize, int destSize)
where TResampler : struct, IResampler
{ {
IResampler resampler = TestUtils.GetResampler(resamplerName); var referenceMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize);
var kernelMap = ResizeKernelMap.Calculate(in resampler, destSize, srcSize, Configuration.Default.MemoryAllocator);
var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize);
var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator);
#if DEBUG #if DEBUG
this.Output.WriteLine(kernelMap.Info); this.Output.WriteLine(kernelMap.Info);
@ -153,11 +153,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
} }
} }
private static string PrintKernelMap(ResizeKernelMap kernelMap) => private static string PrintKernelMap(ResizeKernelMap kernelMap)
PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); => PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i));
private static string PrintKernelMap(ReferenceKernelMap kernelMap) => private static string PrintKernelMap(ReferenceKernelMap kernelMap)
PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); => PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i));
private static string PrintKernelMap<TKernelMap>( private static string PrintKernelMap<TKernelMap>(
TKernelMap kernelMap, TKernelMap kernelMap,

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

@ -121,8 +121,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
configuration.MemoryAllocator = allocator; configuration.MemoryAllocator = allocator;
configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes;
var verticalKernelMap = ResizeKernelMap.Calculate( var verticalKernelMap = ResizeKernelMap.Calculate<BicubicResampler>(
KnownResamplers.Bicubic, default,
destSize.Height, destSize.Height,
image0.Height, image0.Height,
Configuration.Default.MemoryAllocator); Configuration.Default.MemoryAllocator);

8
tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Pad(width, height); this.operations.Pad(width, height);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.TargetWidth); Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.TargetHeight); Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(sampler, resizeProcessor.Sampler);
} }
} }
} }

16
tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs

@ -17,8 +17,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height); this.operations.Resize(width, height);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.TargetWidth); Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.TargetHeight); Assert.Equal(height, resizeProcessor.DestinationHeight);
} }
[Fact] [Fact]
@ -30,8 +30,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height, sampler); this.operations.Resize(width, height, sampler);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.TargetWidth); Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.TargetHeight); Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(sampler, resizeProcessor.Sampler);
} }
@ -47,8 +47,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height, sampler, compand); this.operations.Resize(width, height, sampler, compand);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.TargetWidth); Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.TargetHeight); Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand); Assert.Equal(compand, resizeProcessor.Compand);
} }
@ -73,8 +73,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(resizeOptions); this.operations.Resize(resizeOptions);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.TargetWidth); Assert.Equal(width, resizeProcessor.DestinationWidth);
Assert.Equal(height, resizeProcessor.TargetHeight); Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand); Assert.Equal(compand, resizeProcessor.Compand);

4
tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs

@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.AppendRotationDegrees(builder, degrees); this.AppendRotationDegrees(builder, degrees);
// TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness
Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, size); Matrix3x2 matrix = TransformUtilities.CreateRotationMatrixDegrees(degrees, size);
var position = new Vector2(x, y); var position = new Vector2(x, y);
var expected = Vector2.Transform(position, matrix); var expected = Vector2.Transform(position, matrix);
@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.AppendSkewDegrees(builder, degreesX, degreesY); this.AppendSkewDegrees(builder, degreesX, degreesY);
Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size); Matrix3x2 matrix = TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size);
var position = new Vector2(x, y); var position = new Vector2(x, y);
var expected = Vector2.Transform(position, matrix); var expected = Vector2.Transform(position, matrix);

2
tests/Images/External

@ -1 +1 @@
Subproject commit f9b4bfe42cacb3eefab02ada92ac771a9b93c080 Subproject commit f8a76fd3a900b90c98df67ac896574383a4d09f3
Loading…
Cancel
Save