Browse Source

Apply Transforms 33% faster

pull/1118/head
James Jackson-South 6 years ago
parent
commit
457adb16a4
  1. 12
      src/ImageSharp/Processing/AffineTransformBuilder.cs
  2. 4
      src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs
  3. 34
      src/ImageSharp/Processing/KnownResamplers.cs
  4. 149
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  5. 37
      src/ImageSharp/Processing/Processors/Transforms/IResampler.cs
  6. 145
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
  7. 263
      src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs
  8. 249
      src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs
  9. 47
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs
  10. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs
  11. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs
  12. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs
  13. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs
  14. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs
  15. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs
  16. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs
  17. 38
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs
  18. 44
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs
  19. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs
  20. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs
  21. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs
  22. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs
  23. 39
      src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs
  24. 4
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  25. 4
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  26. 160
      src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs
  27. 13
      src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs
  28. 12
      src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
  29. 4
      tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs

12
src/ImageSharp/Processing/AffineTransformBuilder.cs

@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependRotationRadians(float radians)
=> this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size));
=> this.Prepend(size => TransformUtilities.CreateRotationMatrixRadians(radians, size));
/// <summary>
/// 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>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendRotationRadians(float radians)
=> this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size));
=> this.Append(size => TransformUtilities.CreateRotationMatrixRadians(radians, size));
/// <summary>
/// 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>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY)
=> this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size));
=> this.Prepend(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size));
/// <summary>
/// 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>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY)
=> this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size));
=> this.Prepend(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size));
/// <summary>
/// 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>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY)
=> this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size));
=> this.Append(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size));
/// <summary>
/// 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>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY)
=> this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size));
=> this.Append(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size));
/// <summary>
/// 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)
{
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);
}
@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing
IResampler sampler)
{
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);
}

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

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

@ -1,11 +1,7 @@
// 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
@ -40,149 +36,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <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.GetPixelSpan().CopyTo(destination.GetPixelSpan());
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);
}
}
}
=> this.resampler.ApplyAffineTransform(this.Configuration, source, destination, this.transformMatrix);
}
}

37
src/ImageSharp/Processing/Processors/Transforms/IResampler.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.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
@ -21,5 +24,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The <see cref="float"/>
/// </returns>
float GetValue(float x);
/// <summary>
/// Applies an affine transformation upon an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source image frame.</param>
/// <param name="destination">The destination image frame.</param>
/// <param name="matrix">The transform matrix.</param>
void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Applies a projective transformation upon an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="source">The source image frame.</param>
/// <param name="destination">The destination image frame.</param>
/// <param name="matrix">The transform matrix.</param>
void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel>;
}
}
}

145
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs

@ -1,11 +1,7 @@
// 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
@ -40,145 +36,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <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(Matrix4x4.Identity))
{
// The clone will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return;
}
int width = this.targetSize.Width;
var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration;
// Convert from screen to world space.
Matrix4x4.Invert(this.transformMatrix, out Matrix4x4 matrix);
if (this.resampler is NearestNeighborResampler)
{
Rectangle sourceBounds = this.SourceRectangle;
var nnOperation = new NearestNeighborRowIntervalOperation(sourceBounds, 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);
}
private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly Matrix4x4 matrix;
private readonly int maxX;
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public NearestNeighborRowIntervalOperation(
Rectangle bounds,
ref Matrix4x4 matrix,
int maxX,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination)
{
this.bounds = bounds;
this.matrix = matrix;
this.maxX = maxX;
this.source = source;
this.destination = destination;
}
[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++)
{
Vector2 point = TransformUtils.ProjectiveTransform2D(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 RowIntervalOperation : IRowIntervalOperation<Vector4>
{
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> destination;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
Configuration configuration,
TransformKernelMap kernelMap,
ref Matrix4x4 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;
}
[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.
Vector2 point = TransformUtils.ProjectiveTransform2D(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);
}
}
}
=> this.resampler.ApplyProjectiveTransform(this.Configuration, source, destination, this.transformMatrix);
}
}

263
src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs

@ -0,0 +1,263 @@
// 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
{
/// <content>
/// Extensions for <see cref="IResampler"/>.
/// </content>
public static partial class ResamplerExtensions
{
private readonly struct NNAffineOperation<TPixel> : IRowIntervalOperation
where TPixel : struct, IPixel<TPixel>
{
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 = destination.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 NNProjectiveOperation<TPixel> : IRowIntervalOperation
where TPixel : struct, IPixel<TPixel>
{
private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination;
private readonly Rectangle bounds;
private readonly Matrix4x4 matrix;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)]
public NNProjectiveOperation(
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
{
this.source = source;
this.destination = destination;
this.bounds = destination.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++)
{
Vector2 point = TransformUtilities.ProjectiveTransform2D(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, TPixel> : IRowIntervalOperation<Vector4>
where TResampler : unmanaged, IResampler
where TPixel : struct, IPixel<TPixel>
{
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);
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));
}
}
}
private readonly struct ProjectiveOperation<TResampler, TPixel> : IRowIntervalOperation<Vector4>
where TResampler : unmanaged, IResampler
where TPixel : struct, IPixel<TPixel>
{
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 Matrix4x4 matrix;
private readonly Vector2 radialExtents;
private readonly Vector4 maxSourceExtents;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)]
public ProjectiveOperation(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Buffer2D<float> yKernelBuffer,
Buffer2D<float> xKernelBuffer,
in TResampler sampler,
Matrix4x4 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.
Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix);
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));
}
}
}
}
}

249
src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs

@ -0,0 +1,249 @@
// 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>
/// Extensions for <see cref="IResampler"/>.
/// </summary>
public static partial class ResamplerExtensions
{
/// <summary>
/// Applies an affine transformation upon an image.
/// </summary>
/// <typeparam name="TResampler">The type of sampler.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="sampler">The pixel sampler.</param>
/// <param name="source">The source image frame.</param>
/// <param name="destination">The destination image frame.</param>
/// <param name="matrix">The transform matrix.</param>
public static void ApplyAffineTransform<TResampler, TPixel>(
Configuration configuration,
in TResampler sampler,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TResampler : unmanaged, IResampler
where TPixel : struct, IPixel<TPixel>
{
// 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.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return;
}
// Convert from screen to world space.
Matrix3x2.Invert(matrix, out matrix);
if (sampler is NearestNeighborResampler)
{
var nnOperation = new NNAffineOperation<TPixel>(source, destination, matrix);
ParallelRowIterator.IterateRows(
configuration,
destination.Bounds(),
in nnOperation);
return;
}
int yRadius = GetSamplingRadius(in sampler, source.Height, destination.Height);
int xRadius = 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, TPixel>(
configuration,
source,
destination,
yKernelBuffer,
xKernelBuffer,
in sampler,
matrix,
radialExtents,
maxSourceExtents);
ParallelRowIterator.IterateRows<AffineOperation<TResampler, TPixel>, Vector4>(
configuration,
destination.Bounds(),
in operation);
}
/// <summary>
/// Applies a projective transformation upon an image.
/// </summary>
/// <typeparam name="TResampler">The type of sampler.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="sampler">The pixel sampler.</param>
/// <param name="source">The source image frame.</param>
/// <param name="destination">The destination image frame.</param>
/// <param name="matrix">The transform matrix.</param>
public static void ApplyProjectiveTransform<TResampler, TPixel>(
Configuration configuration,
in TResampler sampler,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TResampler : unmanaged, IResampler
where TPixel : struct, IPixel<TPixel>
{
// Handle transforms that result in output identical to the original.
if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity))
{
// The clone will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return;
}
// Convert from screen to world space.
Matrix4x4.Invert(matrix, out matrix);
if (sampler is NearestNeighborResampler)
{
var nnOperation = new NNProjectiveOperation<TPixel>(source, destination, matrix);
ParallelRowIterator.IterateRows(
configuration,
destination.Bounds(),
in nnOperation);
return;
}
int yRadius = GetSamplingRadius(in sampler, source.Height, destination.Height);
int xRadius = 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 ProjectiveOperation<TResampler, TPixel>(
configuration,
source,
destination,
yKernelBuffer,
xKernelBuffer,
in sampler,
matrix,
radialExtents,
maxSourceExtents);
ParallelRowIterator.IterateRows<ProjectiveOperation<TResampler, TPixel>, Vector4>(
configuration,
destination.Bounds(),
in operation);
}
[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 : unmanaged, 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 : unmanaged, 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;
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetSamplingRadius<TResampler>(in TResampler sampler, int sourceSize, int destinationSize)
where TResampler : unmanaged, IResampler
{
double scale = sourceSize / destinationSize;
if (scale < 1)
{
scale = 1;
}
return (int)Math.Ceiling(scale * sampler.Radius);
}
}
}

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

@ -1,6 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <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.
/// </summary>
public class BicubicResampler : IResampler
public readonly struct BicubicResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x < 0F)
@ -21,21 +26,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
x = -x;
}
float result = 0;
// Given the coefficient "a" as -0.5F.
if (x <= 1F)
{
// 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)
{
// 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 ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

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

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

@ -1,6 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
@ -9,12 +13,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// scale image enlargements that a 'Lagrange' filter can produce.
/// <see href="http://www.imagemagick.org/Usage/filter/#cubic_bc"/>
/// </summary>
public class CatmullRomResampler : IResampler
public readonly struct CatmullRomResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
const float B = 0;
@ -22,5 +27,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

@ -1,6 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// 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
public readonly struct HermiteResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
const float B = 0F;
@ -21,5 +26,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

@ -1,6 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
/// with a radius of 2 pixels.
/// </summary>
public class Lanczos2Resampler : IResampler
public readonly struct Lanczos2Resampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x < 0F)
@ -28,5 +33,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

@ -1,6 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
/// with a radius of 3 pixels.
/// </summary>
public class Lanczos3Resampler : IResampler
public readonly struct Lanczos3Resampler : IResampler
{
/// <inheritdoc/>
public float Radius => 3;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x < 0F)
@ -28,5 +33,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

@ -1,6 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
/// with a radius of 5 pixels.
/// </summary>
public class Lanczos5Resampler : IResampler
public readonly struct Lanczos5Resampler : IResampler
{
/// <inheritdoc/>
public float Radius => 5;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x < 0F)
@ -28,5 +33,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

@ -1,6 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <see href="https://en.wikipedia.org/wiki/Lanczos_resampling#Algorithm">Wikipedia</see>
/// with a radius of 8 pixels.
/// </summary>
public class Lanczos8Resampler : IResampler
public readonly struct Lanczos8Resampler : IResampler
{
/// <inheritdoc/>
public float Radius => 8;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x < 0F)
@ -28,5 +33,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

@ -1,13 +1,17 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
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
public readonly struct MitchellNetravaliResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
@ -20,5 +24,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

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

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

@ -1,18 +1,23 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
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
public readonly struct RobidouxResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
const float B = 0.37821575509399867F;
@ -20,5 +25,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

@ -1,18 +1,23 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
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
public readonly struct RobidouxSharpResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
const float B = 0.2620145123990142F;
@ -20,5 +25,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

@ -1,18 +1,23 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
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
public readonly struct SplineResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 2;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
const float B = 1F;
@ -20,5 +25,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return ImageMaths.GetBcValue(x, B, C);
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

@ -1,6 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// 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.
/// </summary>
public class TriangleResampler : IResampler
public readonly struct TriangleResampler : IResampler
{
/// <inheritdoc/>
public float Radius => 1;
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x < 0F)
@ -28,5 +33,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyAffineTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyAffineTransform(
configuration,
in this,
source,
destination,
matrix);
/// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void ApplyProjectiveTransform<TPixel>(
Configuration configuration,
ImageFrame<TPixel> source,
ImageFrame<TPixel> destination,
Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel> => ResamplerExtensions.ApplyProjectiveTransform(
configuration,
in this,
source,
destination,
matrix);
}
}
}

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

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

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

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

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

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

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 : struct, 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>
/// Contains utility methods for working with transforms.
/// </summary>
internal static class TransformUtils
internal static class TransformUtilities
{
/// <summary>
/// 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="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateRotationMatrixDegrees(float degrees, Size size)
=> CreateCenteredTransformMatrix(
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="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size)
=> CreateCenteredTransformMatrix(
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="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size)
=> CreateCenteredTransformMatrix(
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="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateSkewMatrixRadians(float radiansX, float radiansY, Size size)
=> CreateCenteredTransformMatrix(
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="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Matrix3x2"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 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="fraction">The amount to taper.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction)
{
Matrix4x4 matrix = Matrix4x4.Identity;
@ -225,6 +231,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix)
{
Rectangle transformed = GetTransformedRectangle(rectangle, matrix);
@ -284,6 +291,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix)
{
if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix))
@ -307,6 +315,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <returns>
/// The <see cref="Size"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Size GetTransformedSize(Size size, Matrix4x4 matrix)
{
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);
}
[MethodImpl(InliningOptions.ShortMethod)]
private static Size ConstrainSize(Rectangle rectangle)
{
// 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);
}
[MethodImpl(InliningOptions.ShortMethod)]
private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br)
{
// 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>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
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>
/// 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>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
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>
/// 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>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependRotationRadians(float radians)
=> this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size)));
=> this.Prepend(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size)));
/// <summary>
/// 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>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendRotationRadians(float radians)
=> this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size)));
=> this.Append(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size)));
/// <summary>
/// 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>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
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>
/// 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>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
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>
/// Appends a skew matrix using the given angles in degrees at the given origin.

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

@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.AppendRotationDegrees(builder, degrees);
// 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 expected = Vector2.Transform(position, matrix);
@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
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 expected = Vector2.Transform(position, matrix);

Loading…
Cancel
Save