diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs
new file mode 100644
index 000000000..1d215d286
--- /dev/null
+++ b/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
+ {
+ ///
+ /// Ensures that the value is a value type.
+ ///
+ /// The target object, which cannot be null.
+ /// The name of the parameter that is to be checked.
+ /// The type of the value.
+ /// is not a value type.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void MustBeValueType(TValue value, string parameterName)
+ {
+ if (!value.GetType().GetTypeInfo().IsValueType)
+ {
+ ThrowArgumentException("Type must be a struct.", parameterName);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs
index e7b14be42..a0ce30212 100644
--- a/src/ImageSharp/Common/Helpers/ImageMaths.cs
+++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs
@@ -242,40 +242,6 @@ namespace SixLabors.ImageSharp
return 1F;
}
- ///
- /// Returns the result of a B-C filter against the given value.
- ///
- ///
- /// The value to process.
- /// The B-Spline curve variable.
- /// The Cardinal curve variable.
- ///
- /// The .
- ///
- [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;
- }
-
///
/// Gets the bounding from the given points.
///
diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs
index 90e00924a..dde7beb3e 100644
--- a/src/ImageSharp/Processing/AffineTransformBuilder.cs
+++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs
@@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing
/// The amount of rotation, in radians.
/// The .
public AffineTransformBuilder PrependRotationRadians(float radians)
- => this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size));
+ => this.Prepend(size => TransformUtilities.CreateRotationMatrixRadians(radians, size));
///
/// Prepends a rotation matrix using the given rotation in degrees at the given origin.
@@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing
/// The amount of rotation, in radians.
/// The .
public AffineTransformBuilder AppendRotationRadians(float radians)
- => this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size));
+ => this.Append(size => TransformUtilities.CreateRotationMatrixRadians(radians, size));
///
/// Appends a rotation matrix using the given rotation in degrees at the given origin.
@@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp.Processing
/// The Y angle, in degrees.
/// The .
public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY)
- => this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size));
+ => this.Prepend(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size));
///
/// Prepends a centered skew matrix from the give angles in radians.
@@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Processing
/// The Y angle, in radians.
/// The .
public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY)
- => this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size));
+ => this.Prepend(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size));
///
/// Prepends a skew matrix using the given angles in degrees at the given origin.
@@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.Processing
/// The Y angle, in degrees.
/// The .
public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY)
- => this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size));
+ => this.Append(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size));
///
/// Appends a centered skew matrix from the give angles in radians.
@@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Processing
/// The Y angle, in radians.
/// The .
public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY)
- => this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size));
+ => this.Append(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size));
///
/// Appends a skew matrix using the given angles in degrees at the given origin.
diff --git a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs
index 630564955..ee8f3854e 100644
--- a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs
+++ b/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);
}
diff --git a/src/ImageSharp/Processing/KnownResamplers.cs b/src/ImageSharp/Processing/KnownResamplers.cs
index 621215b28..348c08407 100644
--- a/src/ImageSharp/Processing/KnownResamplers.cs
+++ b/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
///
/// Gets the Bicubic sampler that implements the bicubic kernel algorithm W(x)
///
- public static IResampler Bicubic { get; } = new BicubicResampler();
+ public static IResampler Bicubic { get; } = default(BicubicResampler);
///
/// Gets the Box sampler that implements the box algorithm. Similar to nearest neighbor when upscaling.
/// When downscaling the pixels will average, merging pixels together.
///
- public static IResampler Box { get; } = new BoxResampler();
+ public static IResampler Box { get; } = default(BoxResampler);
///
/// Gets the Catmull-Rom sampler, a well known standard Cubic Filter often used as a interpolation function
///
- public static IResampler CatmullRom { get; } = new CatmullRomResampler();
+ public static IResampler CatmullRom { get; } = CubicResampler.CatmullRom;
///
/// 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.
///
- public static IResampler Hermite { get; } = new HermiteResampler();
+ public static IResampler Hermite { get; } = CubicResampler.Hermite;
///
/// 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.
///
- public static IResampler Lanczos2 { get; } = new Lanczos2Resampler();
+ public static IResampler Lanczos2 { get; } = LanczosResampler.Lanczos2;
///
/// 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.
///
- public static IResampler Lanczos3 { get; } = new Lanczos3Resampler();
+ public static IResampler Lanczos3 { get; } = LanczosResampler.Lanczos3;
///
/// 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.
///
- public static IResampler Lanczos5 { get; } = new Lanczos5Resampler();
+ public static IResampler Lanczos5 { get; } = LanczosResampler.Lanczos5;
///
/// 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.
///
- public static IResampler Lanczos8 { get; } = new Lanczos8Resampler();
+ public static IResampler Lanczos8 { get; } = LanczosResampler.Lanczos8;
///
/// Gets the Mitchell-Netravali sampler. This seperable cubic algorithm yields a very good equilibrium between
/// detail preservation (sharpness) and smoothness.
///
- public static IResampler MitchellNetravali { get; } = new MitchellNetravaliResampler();
+ public static IResampler MitchellNetravali { get; } = CubicResampler.MitchellNetravali;
///
/// 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.
///
- public static IResampler NearestNeighbor { get; } = new NearestNeighborResampler();
+ public static IResampler NearestNeighbor { get; } = default(NearestNeighborResampler);
///
/// Gets the Robidoux sampler. This algorithm developed by Nicolas Robidoux providing a very good equilibrium between
/// detail preservation (sharpness) and smoothness comparable to .
///
- public static IResampler Robidoux { get; } = new RobidouxResampler();
+ public static IResampler Robidoux { get; } = CubicResampler.Robidoux;
///
/// Gets the Robidoux Sharp sampler. A sharpened form of the sampler
///
- public static IResampler RobidouxSharp { get; } = new RobidouxSharpResampler();
+ public static IResampler RobidouxSharp { get; } = CubicResampler.RobidouxSharp;
///
- /// Gets the Spline sampler. A seperable cubic algorithm similar to but yielding smoother results.
+ /// Gets the Spline sampler. A separable cubic algorithm similar to but yielding smoother results.
///
- public static IResampler Spline { get; } = new SplineResampler();
+ public static IResampler Spline { get; } = CubicResampler.Spline;
///
/// 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
///
- public static IResampler Triangle { get; } = new TriangleResampler();
+ public static IResampler Triangle { get; } = default(TriangleResampler);
///
/// Gets the Welch sampler. A high speed algorithm that delivers very sharpened results.
///
- public static IResampler Welch { get; } = new WelchResampler();
+ public static IResampler Welch { get; } = default(WelchResampler);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
index c539861f9..1b6438bf3 100644
--- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
@@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
///
- /// Gets the size of the target image.
+ /// Gets the size of the destination image.
///
/// The .
- protected abstract Size GetTargetSize();
+ protected abstract Size GetDestinationSize();
///
/// This method is called before the process is applied to prepare the processor.
@@ -168,21 +168,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
private Image CreateTarget()
{
Image 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.
- var targetFrames = new ImageFrame[source.Frames.Count];
- for (int i = 0; i < targetFrames.Length; i++)
+ var destinationFrames = new ImageFrame[source.Frames.Count];
+ for (int i = 0; i < destinationFrames.Length; i++)
{
- targetFrames[i] = new ImageFrame(
+ destinationFrames[i] = new ImageFrame(
this.Configuration,
- targetSize.Width,
- targetSize.Height,
+ destinationSize.Width,
+ destinationSize.Height,
source.Frames[i].Metadata.DeepClone());
}
// Use the overload to prevent an extra frame being added.
- return new Image(this.Configuration, source.Metadata.DeepClone(), targetFrames);
+ return new Image(this.Configuration, source.Metadata.DeepClone(), destinationFrames);
}
private void CheckFrameCount(Image a, Image b)
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
deleted file mode 100644
index 574d3cb18..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Provides the base methods to perform affine transforms on an image.
- ///
- /// The pixel format.
- internal class AffineTransformProcessor : TransformProcessor
- where TPixel : struct, IPixel
- {
- private readonly Size targetSize;
- private readonly Matrix3x2 transformMatrix;
- private readonly IResampler resampler;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The configuration which allows altering default behaviour or extending the library.
- /// The defining the processor parameters.
- /// The source for the current processor instance.
- /// The source area to process for the current processor instance.
- public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image 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;
-
- ///
- protected override void OnFrameApply(ImageFrame source, ImageFrame 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(
- configuration,
- targetBounds,
- in operation);
- }
-
- ///
- /// A implementing the nearest neighbor resampler logic for .
- ///
- private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation
- {
- private readonly Rectangle bounds;
- private readonly Matrix3x2 matrix;
- private readonly int maxX;
- private readonly ImageFrame source;
- private readonly ImageFrame destination;
-
- [MethodImpl(InliningOptions.ShortMethod)]
- public NearestNeighborRowIntervalOperation(
- Rectangle bounds,
- ref Matrix3x2 matrix,
- int maxX,
- ImageFrame source,
- ImageFrame 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 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];
- }
- }
- }
- }
- }
-
- ///
- /// A implementing the transformation logic for .
- ///
- private readonly struct RowIntervalOperation : IRowIntervalOperation
- {
- private readonly Configuration configuration;
- private readonly TransformKernelMap kernelMap;
- private readonly Matrix3x2 matrix;
- private readonly int maxX;
- private readonly ImageFrame source;
- private readonly ImageFrame destination;
-
- [MethodImpl(InliningOptions.ShortMethod)]
- public RowIntervalOperation(
- Configuration configuration,
- TransformKernelMap kernelMap,
- ref Matrix3x2 matrix,
- int maxX,
- ImageFrame source,
- ImageFrame 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 span)
- {
- for (int y = rows.Min; y < rows.Max; y++)
- {
- Span targetRowSpan = this.destination.GetPixelRowSpan(y);
- PixelOperations.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.Instance.FromVector4Destructive(
- this.configuration,
- span,
- targetRowSpan);
- }
- }
- }
- }
-}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
index e8eeea3cb..a80eef2bc 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
@@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
=> this.cropRectangle = definition.CropRectangle;
///
- protected override Size GetTargetSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height);
+ protected override Size GetDestinationSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height);
///
protected override void OnFrameApply(ImageFrame source, ImageFrame destination)
diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs
index 6db03d5b4..c0c3be869 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs
+++ b/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.
+using SixLabors.ImageSharp.PixelFormats;
+
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
@@ -21,5 +23,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The
///
float GetValue(float x);
+
+ ///
+ /// Applies a transformation upon an image.
+ ///
+ /// The pixel format.
+ /// The transforming image processor.
+ void ApplyTransform(IResamplingTransformImageProcessor processor)
+ where TPixel : struct, IPixel;
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs
new file mode 100644
index 000000000..ab750f7fb
--- /dev/null
+++ b/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
+{
+ ///
+ /// Implements an algorithm to alter the pixels of an image via resampling transforms.
+ ///
+ /// The pixel format.
+ public interface IResamplingTransformImageProcessor : IImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// Applies a resampling transform with the given sampler.
+ ///
+ /// The type of sampler.
+ /// The sampler to use.
+ void ApplyTransform(in TResampler sampler)
+ where TResampler : struct, IResampler;
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs
similarity index 87%
rename from src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs
index 849f06166..fec41dbff 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
+++ b/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)
{
Guard.NotNull(sampler, nameof(sampler));
+ Guard.MustBeValueType(sampler, nameof(sampler));
+
this.Sampler = sampler;
this.TransformMatrix = matrix;
- this.TargetDimensions = targetDimensions;
+ this.DestinationSize = targetDimensions;
}
///
@@ -35,9 +37,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Matrix3x2 TransformMatrix { get; }
///
- /// Gets the target dimensions to constrain the transformed image to.
+ /// Gets the destination size to constrain the transformed image to.
///
- public Size TargetDimensions { get; }
+ public Size DestinationSize { get; }
///
public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle)
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
new file mode 100644
index 000000000..72bfa4c0b
--- /dev/null
+++ b/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
+{
+ ///
+ /// Provides the base methods to perform affine transforms on an image.
+ ///
+ /// The pixel format.
+ internal class AffineTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor
+ where TPixel : struct, IPixel
+ {
+ private readonly Size destinationSize;
+ private readonly Matrix3x2 transformMatrix;
+ private readonly IResampler resampler;
+ private ImageFrame source;
+ private ImageFrame destination;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration which allows altering default behaviour or extending the library.
+ /// The defining the processor parameters.
+ /// The source for the current processor instance.
+ /// The source area to process for the current processor instance.
+ public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image 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;
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, ImageFrame destination)
+ {
+ this.source = source;
+ this.destination = destination;
+ this.resampler.ApplyTransform(this);
+ }
+
+ ///
+ public void ApplyTransform(in TResampler sampler)
+ where TResampler : struct, IResampler
+ {
+ Configuration configuration = this.Configuration;
+ ImageFrame source = this.source;
+ ImageFrame 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 yKernelBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height);
+ using Buffer2D xKernelBuffer = configuration.MemoryAllocator.Allocate2D(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(
+ configuration,
+ source,
+ destination,
+ yKernelBuffer,
+ xKernelBuffer,
+ in sampler,
+ matrix,
+ radialExtents,
+ maxSourceExtents);
+
+ ParallelRowIterator.IterateRows, Vector4>(
+ configuration,
+ destination.Bounds(),
+ in operation);
+ }
+
+ private readonly struct NNAffineOperation : IRowIntervalOperation
+ {
+ private readonly ImageFrame source;
+ private readonly ImageFrame destination;
+ private readonly Rectangle bounds;
+ private readonly Matrix3x2 matrix;
+ private readonly int maxX;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public NNAffineOperation(
+ ImageFrame source,
+ ImageFrame 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 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 : IRowIntervalOperation
+ where TResampler : struct, IResampler
+ {
+ private readonly Configuration configuration;
+ private readonly ImageFrame source;
+ private readonly ImageFrame destination;
+ private readonly Buffer2D yKernelBuffer;
+ private readonly Buffer2D 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 source,
+ ImageFrame destination,
+ Buffer2D yKernelBuffer,
+ Buffer2D 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 span)
+ {
+ Buffer2D sourceBuffer = this.source.PixelBuffer;
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ PixelOperations.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.Instance.FromVector4Destructive(
+ this.configuration,
+ span,
+ this.destination.GetPixelRowSpan(y));
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs
similarity index 100%
rename from src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs
diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs
similarity index 100%
rename from src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs
diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs
similarity index 100%
rename from src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs
diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs
similarity index 100%
rename from src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs
new file mode 100644
index 000000000..4fb1e27e0
--- /dev/null
+++ b/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
+{
+ ///
+ /// Utility methods for affine and projective transforms.
+ ///
+ internal static class LinearTransformUtilities
+ {
+ [MethodImpl(InliningOptions.ShortMethod)]
+ internal static int GetSamplingRadius(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(
+ in TResampler sampler,
+ Vector2 transformedPoint,
+ Buffer2D sourcePixels,
+ Span targetRow,
+ int column,
+ ref float yKernelSpanRef,
+ ref float xKernelSpanRef,
+ Vector2 radialExtents,
+ Vector4 maxSourceExtents)
+ where TResampler : struct, IResampler
+ where TPixel : struct, IPixel
+ {
+ // 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(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;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs
similarity index 87%
rename from src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs
index d8a9c3ed9..f716ba701 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
+++ b/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)
{
Guard.NotNull(sampler, nameof(sampler));
+ Guard.MustBeValueType(sampler, nameof(sampler));
+
this.Sampler = sampler;
this.TransformMatrix = matrix;
- this.TargetDimensions = targetDimensions;
+ this.DestinationSize = targetDimensions;
}
///
@@ -35,9 +37,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Matrix4x4 TransformMatrix { get; }
///
- /// Gets the target dimensions to constrain the transformed image to.
+ /// Gets the destination size to constrain the transformed image to.
///
- public Size TargetDimensions { get; }
+ public Size DestinationSize { get; }
///
public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle)
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
similarity index 50%
rename from src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
index 175615ebd..b3315fa55 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
@@ -4,6 +4,7 @@
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;
@@ -14,12 +15,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Provides the base methods to perform non-affine transforms on an image.
///
/// The pixel format.
- internal class ProjectiveTransformProcessor : TransformProcessor
+ internal class ProjectiveTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor
where TPixel : struct, IPixel
{
- private readonly Size targetSize;
+ private readonly Size destinationSize;
private readonly IResampler resampler;
private readonly Matrix4x4 transformMatrix;
+ private ImageFrame source;
+ private ImageFrame destination;
///
/// Initializes a new instance of the class.
@@ -31,74 +34,102 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public ProjectiveTransformProcessor(Configuration configuration, ProjectiveTransformProcessor definition, Image source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
- this.targetSize = definition.TargetDimensions;
+ this.destinationSize = definition.DestinationSize;
this.transformMatrix = definition.TransformMatrix;
this.resampler = definition.Sampler;
}
- protected override Size GetTargetSize() => this.targetSize;
+ protected override Size GetDestinationSize() => this.destinationSize;
///
protected override void OnFrameApply(ImageFrame source, ImageFrame destination)
{
+ this.source = source;
+ this.destination = destination;
+ this.resampler.ApplyTransform(this);
+ }
+
+ ///
+ public void ApplyTransform(in TResampler sampler)
+ where TResampler : struct, IResampler
+ {
+ Configuration configuration = this.Configuration;
+ ImageFrame source = this.source;
+ ImageFrame destination = this.destination;
+ Matrix4x4 matrix = this.transformMatrix;
+
// 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
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.
- 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 NearestNeighborRowIntervalOperation(sourceBounds, ref matrix, width, source, destination);
+ var nnOperation = new NNProjectiveOperation(source, destination, matrix);
ParallelRowIterator.IterateRows(
configuration,
- targetBounds,
+ destination.Bounds(),
in nnOperation);
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 yKernelBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height);
+ using Buffer2D xKernelBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height);
- var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination);
- ParallelRowIterator.IterateRows(
+ int maxX = source.Width - 1;
+ int maxY = source.Height - 1;
+ var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY);
+
+ var operation = new ProjectiveOperation(
+ configuration,
+ source,
+ destination,
+ yKernelBuffer,
+ xKernelBuffer,
+ in sampler,
+ matrix,
+ radialExtents,
+ maxSourceExtents);
+
+ ParallelRowIterator.IterateRows, Vector4>(
configuration,
- targetBounds,
+ destination.Bounds(),
in operation);
}
- private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation
+ private readonly struct NNProjectiveOperation : IRowIntervalOperation
{
+ private readonly ImageFrame source;
+ private readonly ImageFrame destination;
private readonly Rectangle bounds;
private readonly Matrix4x4 matrix;
private readonly int maxX;
- private readonly ImageFrame source;
- private readonly ImageFrame destination;
[MethodImpl(InliningOptions.ShortMethod)]
- public NearestNeighborRowIntervalOperation(
- Rectangle bounds,
- ref Matrix4x4 matrix,
- int maxX,
+ public NNProjectiveOperation(
ImageFrame source,
- ImageFrame destination)
+ ImageFrame destination,
+ Matrix4x4 matrix)
{
- this.bounds = bounds;
- this.matrix = matrix;
- this.maxX = maxX;
this.source = source;
this.destination = destination;
+ this.bounds = source.Bounds();
+ this.matrix = matrix;
+ this.maxX = destination.Width;
}
[MethodImpl(InliningOptions.ShortMethod)]
@@ -110,7 +141,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
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 py = (int)MathF.Round(point.Y);
@@ -123,60 +154,79 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
}
- private readonly struct RowIntervalOperation : IRowIntervalOperation
+ private readonly struct ProjectiveOperation : IRowIntervalOperation
+ where TResampler : struct, IResampler
{
private readonly Configuration configuration;
- private readonly TransformKernelMap kernelMap;
- private readonly Matrix4x4 matrix;
- private readonly int maxX;
private readonly ImageFrame source;
private readonly ImageFrame destination;
+ private readonly Buffer2D yKernelBuffer;
+ private readonly Buffer2D 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 RowIntervalOperation(
+ public ProjectiveOperation(
Configuration configuration,
- TransformKernelMap kernelMap,
- ref Matrix4x4 matrix,
- int maxX,
ImageFrame source,
- ImageFrame destination)
+ ImageFrame destination,
+ Buffer2D yKernelBuffer,
+ Buffer2D xKernelBuffer,
+ in TResampler sampler,
+ Matrix4x4 matrix,
+ Vector2 radialExtents,
+ Vector4 maxSourceExtents)
{
this.configuration = configuration;
- this.kernelMap = kernelMap;
- this.matrix = matrix;
- this.maxX = maxX;
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 span)
{
+ Buffer2D sourceBuffer = this.source.PixelBuffer;
for (int y = rows.Min; y < rows.Max; y++)
{
- Span targetRowSpan = this.destination.GetPixelRowSpan(y);
- PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan, span);
- ref float ySpanRef = ref this.kernelMap.GetYStartReference(y);
- ref float xSpanRef = ref this.kernelMap.GetXStartReference(y);
+ PixelOperations.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 = TransformUtils.ProjectiveTransform2D(x, y, this.matrix);
- this.kernelMap.Convolve(
+ Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix);
+ LinearTransformUtilities.Convolve(
+ in this.sampler,
point,
+ sourceBuffer,
+ span,
x,
- ref ySpanRef,
- ref xSpanRef,
- this.source.PixelBuffer,
- span);
+ ref yKernelSpanRef,
+ ref xKernelSpanRef,
+ this.radialExtents,
+ this.maxSourceExtents);
}
PixelOperations.Instance.FromVector4Destructive(
this.configuration,
span,
- targetRowSpan);
+ this.destination.GetPixelRowSpan(y));
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs
similarity index 90%
rename from src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs
index aae66e9ea..b53e7b5c0 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs
@@ -28,14 +28,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The source image size
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))
{
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs
similarity index 100%
rename from src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs
similarity index 91%
rename from src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
rename to src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs
index 4d0733334..1bcfa5fd2 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs
@@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The source image size
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))
{
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs
index 199563bc7..085c81aad 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs
+++ b/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.
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.PixelFormats;
+
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
@@ -8,12 +11,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Wikipedia
/// A commonly used algorithm within image processing that preserves sharpness better than triangle interpolation.
///
- public class BicubicResampler : IResampler
+ public readonly struct BicubicResampler : IResampler
{
///
public float Radius => 2;
///
+ [MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x < 0F)
@@ -21,21 +25,25 @@ 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;
}
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void ApplyTransform(IResamplingTransformImageProcessor processor)
+ where TPixel : struct, IPixel
+ => processor.ApplyTransform(in this);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs
index 0667226d9..af2abb5f4 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs
+++ b/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.
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.PixelFormats;
+
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
/// The function implements the box algorithm. Similar to nearest neighbor when upscaling.
/// When downscaling the pixels will average, merging together.
///
- public class BoxResampler : IResampler
+ public readonly struct BoxResampler : IResampler
{
///
public float Radius => 0.5F;
///
+ [MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x > -0.5F && x <= 0.5F)
@@ -22,5 +26,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0;
}
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void ApplyTransform(IResamplingTransformImageProcessor processor)
+ where TPixel : struct, IPixel
+ => processor.ApplyTransform(in this);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs
deleted file mode 100644
index 8995d2d8a..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// 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.
- ///
- ///
- public class CatmullRomResampler : IResampler
- {
- ///
- public float Radius => 2;
-
- ///
- public float GetValue(float x)
- {
- const float B = 0;
- const float C = 0.5F;
-
- return ImageMaths.GetBcValue(x, B, C);
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs
new file mode 100644
index 000000000..b39932674
--- /dev/null
+++ b/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
+{
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ public readonly struct CubicResampler : IResampler
+ {
+ private readonly float bspline;
+ private readonly float cardinal;
+
+ ///
+ /// 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.
+ ///
+ public static CubicResampler CatmullRom = new CubicResampler(2, 0, .5F);
+
+ ///
+ /// 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.
+ ///
+ public static CubicResampler Hermite = new CubicResampler(2, 0, 0);
+
+ ///
+ /// The function implements the Mitchell-Netravali algorithm as described on
+ /// Wikipedia
+ ///
+ public static CubicResampler MitchellNetravali = new CubicResampler(2, .3333333F, .3333333F);
+
+ ///
+ /// The function implements the Robidoux algorithm.
+ ///
+ ///
+ public static CubicResampler Robidoux = new CubicResampler(2, .37821575509399867F, .31089212245300067F);
+
+ ///
+ /// The function implements the Robidoux Sharp algorithm.
+ ///
+ ///
+ public static CubicResampler RobidouxSharp = new CubicResampler(2, .2620145123990142F, .3689927438004929F);
+
+ ///
+ /// The function implements the spline algorithm.
+ ///
+ ///
+ ///
+ /// The function implements the Robidoux Sharp algorithm.
+ ///
+ ///
+ public static CubicResampler Spline = new CubicResampler(2, 1, 0);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The sampling radius.
+ /// The B-Spline value.
+ /// The Cardinal cubic value.
+ public CubicResampler(float radius, float bspline, float cardinal)
+ {
+ this.Radius = radius;
+ this.bspline = bspline;
+ this.cardinal = cardinal;
+ }
+
+ ///
+ public float Radius { get; }
+
+ ///
+ [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;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void ApplyTransform(IResamplingTransformImageProcessor processor)
+ where TPixel : struct, IPixel
+ => processor.ApplyTransform(in this);
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs
deleted file mode 100644
index 18c3fda7c..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// 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.
- ///
- ///
- public class HermiteResampler : IResampler
- {
- ///
- public float Radius => 2;
-
- ///
- public float GetValue(float x)
- {
- const float B = 0F;
- const float C = 0F;
-
- return ImageMaths.GetBcValue(x, B, C);
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs
deleted file mode 100644
index 2294696de..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// The function implements the Lanczos kernel algorithm as described on
- /// Wikipedia
- /// with a radius of 2 pixels.
- ///
- public class Lanczos2Resampler : IResampler
- {
- ///
- public float Radius => 2;
-
- ///
- public float GetValue(float x)
- {
- if (x < 0F)
- {
- x = -x;
- }
-
- if (x < 2F)
- {
- return ImageMaths.SinC(x) * ImageMaths.SinC(x / 2F);
- }
-
- return 0F;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs
deleted file mode 100644
index 95fb206a9..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// The function implements the Lanczos kernel algorithm as described on
- /// Wikipedia
- /// with a radius of 3 pixels.
- ///
- public class Lanczos3Resampler : IResampler
- {
- ///
- public float Radius => 3;
-
- ///
- public float GetValue(float x)
- {
- if (x < 0F)
- {
- x = -x;
- }
-
- if (x < 3F)
- {
- return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3F);
- }
-
- return 0F;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs
deleted file mode 100644
index c99ed1e85..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// The function implements the Lanczos kernel algorithm as described on
- /// Wikipedia
- /// with a radius of 5 pixels.
- ///
- public class Lanczos5Resampler : IResampler
- {
- ///
- public float Radius => 5;
-
- ///
- public float GetValue(float x)
- {
- if (x < 0F)
- {
- x = -x;
- }
-
- if (x < 5F)
- {
- return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5F);
- }
-
- return 0F;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs
deleted file mode 100644
index 4efdb882b..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// The function implements the Lanczos kernel algorithm as described on
- /// Wikipedia
- /// with a radius of 8 pixels.
- ///
- public class Lanczos8Resampler : IResampler
- {
- ///
- public float Radius => 8;
-
- ///
- public float GetValue(float x)
- {
- if (x < 0F)
- {
- x = -x;
- }
-
- if (x < 8F)
- {
- return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8F);
- }
-
- return 0F;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs
new file mode 100644
index 000000000..202edcd36
--- /dev/null
+++ b/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
+{
+ ///
+ /// The function implements the Lanczos kernel algorithm as described on
+ /// Wikipedia.
+ ///
+ public readonly struct LanczosResampler : IResampler
+ {
+ ///
+ /// Implements the Lanczos kernel algorithm with a radius of 2.
+ ///
+ public static LanczosResampler Lanczos2 = new LanczosResampler(2);
+
+ ///
+ /// Implements the Lanczos kernel algorithm with a radius of 3.
+ ///
+ public static LanczosResampler Lanczos3 = new LanczosResampler(3);
+
+ ///
+ /// Implements the Lanczos kernel algorithm with a radius of 5.
+ ///
+ public static LanczosResampler Lanczos5 = new LanczosResampler(5);
+
+ ///
+ /// Implements the Lanczos kernel algorithm with a radius of 8.
+ ///
+ public static LanczosResampler Lanczos8 = new LanczosResampler(8);
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The sampling radius.
+ public LanczosResampler(float radius) => this.Radius = radius;
+
+ ///
+ public float Radius { get; }
+
+ ///
+ [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;
+ }
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void ApplyTransform(IResamplingTransformImageProcessor processor)
+ where TPixel : struct, IPixel
+ => processor.ApplyTransform(in this);
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs
deleted file mode 100644
index d4ba954f2..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// The function implements the mitchell algorithm as described on
- /// Wikipedia
- ///
- public class MitchellNetravaliResampler : IResampler
- {
- ///
- public float Radius => 2;
-
- ///
- public float GetValue(float x)
- {
- const float B = 0.3333333F;
- const float C = 0.3333333F;
-
- return ImageMaths.GetBcValue(x, B, C);
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs
index 1f12334f4..0bce3d129 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs
+++ b/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.
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.PixelFormats;
+
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
/// The function implements the nearest neighbor algorithm. This uses an unscaled filter
/// which will select the closest pixel to the new pixels position.
///
- public class NearestNeighborResampler : IResampler
+ public readonly struct NearestNeighborResampler : IResampler
{
///
public float Radius => 1;
///
- public float GetValue(float x)
- {
- return x;
- }
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public float GetValue(float x) => x;
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void ApplyTransform(IResamplingTransformImageProcessor processor)
+ where TPixel : struct, IPixel
+ => processor.ApplyTransform(in this);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs
deleted file mode 100644
index 51938566c..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// The function implements the Robidoux algorithm.
- ///
- ///
- public class RobidouxResampler : IResampler
- {
- ///
- public float Radius => 2;
-
- ///
- public float GetValue(float x)
- {
- const float B = 0.37821575509399867F;
- const float C = 0.31089212245300067F;
-
- return ImageMaths.GetBcValue(x, B, C);
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs
deleted file mode 100644
index 015b7f0af..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// The function implements the Robidoux Sharp algorithm.
- ///
- ///
- public class RobidouxSharpResampler : IResampler
- {
- ///
- public float Radius => 2;
-
- ///
- public float GetValue(float x)
- {
- const float B = 0.2620145123990142F;
- const float C = 0.3689927438004929F;
-
- return ImageMaths.GetBcValue(x, B, C);
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs
deleted file mode 100644
index df6c2a338..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Processing.Processors.Transforms
-{
- ///
- /// The function implements the spline algorithm.
- ///
- ///
- public class SplineResampler : IResampler
- {
- ///
- public float Radius => 2;
-
- ///
- public float GetValue(float x)
- {
- const float B = 1F;
- const float C = 0F;
-
- return ImageMaths.GetBcValue(x, B, C);
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs
index 57d1fa11d..42459d4a3 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs
+++ b/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.
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.PixelFormats;
+
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
@@ -8,12 +11,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.
///
- public class TriangleResampler : IResampler
+ public readonly struct TriangleResampler : IResampler
{
///
public float Radius => 1;
///
+ [MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x < 0F)
@@ -28,5 +32,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void ApplyTransform(IResamplingTransformImageProcessor processor)
+ where TPixel : struct, IPixel
+ => processor.ApplyTransform(in this);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs
index edce5fcf9..6142dbe06 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs
+++ b/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.
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.PixelFormats;
+
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
/// The function implements the welch algorithm.
///
///
- public class WelchResampler : IResampler
+ public readonly struct WelchResampler : IResampler
{
///
public float Radius => 3;
///
+ [MethodImpl(InliningOptions.ShortMethod)]
public float GetValue(float x)
{
if (x < 0F)
@@ -27,5 +31,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return 0F;
}
+
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void ApplyTransform(IResamplingTransformImageProcessor processor)
+ where TPixel : struct, IPixel
+ => processor.ApplyTransform(in this);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
index 14bf552b9..f3521ebed 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
+++ b/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.
using System;
@@ -28,12 +28,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
/// Gets the start index for the destination row.
///
- public int StartIndex { get; }
+ public int StartIndex
+ {
+ [MethodImpl(InliningOptions.ShortMethod)]
+ get;
+ }
///
/// Gets the the length of the kernel.
///
- public int Length { get; }
+ public int Length
+ {
+ [MethodImpl(InliningOptions.ShortMethod)]
+ get;
+ }
///
/// Gets the span representing the portion of the that this window covers.
@@ -81,6 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Copy the contents of altering
/// to the value .
///
+ [MethodImpl(InliningOptions.ShortMethod)]
internal ResizeKernel AlterLeftValue(int left)
{
return new ResizeKernel(left, this.bufferPtr, this.Length);
@@ -96,4 +105,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
index be2546369..a79f60339 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs
+++ b/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.
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
- ///
- /// Contains
- ///
internal partial class ResizeKernelMap
{
///
@@ -21,7 +18,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public PeriodicKernelMap(
MemoryAllocator memoryAllocator,
- IResampler sampler,
int sourceLength,
int destinationLength,
double ratio,
@@ -31,7 +27,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int cornerInterval)
: base(
memoryAllocator,
- sampler,
sourceLength,
destinationLength,
(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}";
- protected override void Initialize()
+ protected internal override void Initialize(in TResampler sampler)
{
// Build top corner data + one period of the mosaic data:
int startOfFirstRepeatedMosaic = this.cornerInterval + this.period;
for (int i = 0; i < startOfFirstRepeatedMosaic; i++)
{
- ResizeKernel kernel = this.BuildKernel(i, i);
- this.kernels[i] = kernel;
+ this.kernels[i] = this.BuildKernel(in sampler, i, i);
}
// Copy the mosaics:
@@ -70,10 +64,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int bottomStartData = this.cornerInterval + this.period;
for (int i = 0; i < this.cornerInterval; i++)
{
- ResizeKernel kernel = this.BuildKernel(bottomStartDest + i, bottomStartData + i);
- this.kernels[bottomStartDest + i] = kernel;
+ this.kernels[bottomStartDest + i] = this.BuildKernel(in sampler, bottomStartDest + i, bottomStartData + i);
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
index cc9516956..3e7ccbd0a 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
@@ -5,21 +5,17 @@ using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
///
- /// Provides values from an optimized,
- /// contiguous memory region.
+ /// Provides resize kernel values from an optimized contiguous memory region.
///
internal partial class ResizeKernelMap : IDisposable
{
private static readonly TolerantMath TolerantMath = TolerantMath.Default;
- private readonly IResampler sampler;
-
private readonly int sourceLength;
private readonly double ratio;
@@ -34,12 +30,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ResizeKernel[] kernels;
+ private bool isDisposed;
+
// To avoid both GC allocations, and MemoryAllocator ceremony:
private readonly double[] tempValues;
private ResizeKernelMap(
MemoryAllocator memoryAllocator,
- IResampler sampler,
int sourceLength,
int destinationLength,
int bufferHeight,
@@ -47,7 +44,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
double scale,
int radius)
{
- this.sampler = sampler;
this.ratio = ratio;
this.scale = scale;
this.radius = radius;
@@ -80,30 +76,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Disposes instance releasing it's backing buffer.
///
public void Dispose()
+ => this.Dispose(true);
+
+ ///
+ /// Disposes the object and frees resources for the Garbage Collector.
+ ///
+ /// Whether to dispose of managed and unmanaged objects.
+ protected virtual void Dispose(bool disposing)
{
- this.pinHandle.Dispose();
- this.data.Dispose();
+ if (!this.isDisposed)
+ {
+ this.isDisposed = true;
+
+ if (disposing)
+ {
+ this.pinHandle.Dispose();
+ this.data.Dispose();
+ }
+ }
}
///
/// Returns a for an index value between 0 and DestinationSize - 1.
///
[MethodImpl(InliningOptions.ShortMethod)]
- public ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx];
+ internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx];
///
/// Computes the weights to apply at each pixel when resizing.
///
+ /// The type of sampler.
/// The
/// The destination size
/// The source size
/// The to use for buffer allocations
/// The
- public static ResizeKernelMap Calculate(
- IResampler sampler,
+ public static ResizeKernelMap Calculate(
+ in TResampler sampler,
int destinationSize,
int sourceSize,
MemoryAllocator memoryAllocator)
+ where TResampler : struct, IResampler
{
double ratio = (double)sourceSize / destinationSize;
double scale = ratio;
@@ -144,7 +157,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
ResizeKernelMap result = hasAtLeast2Periods
? new PeriodicKernelMap(
memoryAllocator,
- sampler,
sourceSize,
destinationSize,
ratio,
@@ -154,7 +166,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
cornerInterval)
: new ResizeKernelMap(
memoryAllocator,
- sampler,
sourceSize,
destinationSize,
destinationSize,
@@ -162,17 +173,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
scale,
radius);
- result.Initialize();
+ result.Initialize(in sampler);
return result;
}
- protected virtual void Initialize()
+ ///
+ /// Initializes the kernel map.
+ ///
+ protected internal virtual void Initialize(in TResampler sampler)
+ where TResampler : struct, IResampler
{
for (int i = 0; i < this.DestinationLength; i++)
{
- ResizeKernel kernel = this.BuildKernel(i, i);
- this.kernels[i] = kernel;
+ this.kernels[i] = this.BuildKernel(in sampler, i, i);
}
}
@@ -181,7 +195,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// referencing the data at row within ,
/// so the data reusable by other data rows.
///
- private ResizeKernel BuildKernel(int destRowIndex, int dataRowIndex)
+ private ResizeKernel BuildKernel(in TResampler sampler, int destRowIndex, int dataRowIndex)
+ where TResampler : struct, IResampler
{
double center = ((destRowIndex + .5) * this.ratio) - .5;
@@ -205,7 +220,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
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;
kernelValues[j - left] = value;
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
index ec1f94c14..4e6e7a48c 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
+++ b/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.Sampler, nameof(options.Sampler));
+ Guard.MustBeValueType(options.Sampler, nameof(options.Sampler));
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options);
this.Sampler = options.Sampler;
- this.TargetWidth = size.Width;
- this.TargetHeight = size.Height;
- this.TargetRectangle = rectangle;
+ this.DestinationWidth = size.Width;
+ this.DestinationHeight = size.Height;
+ this.DestinationRectangle = rectangle;
this.Compand = options.Compand;
}
@@ -33,19 +34,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public IResampler Sampler { get; }
///
- /// Gets the target width.
+ /// Gets the destination width.
///
- public int TargetWidth { get; }
+ public int DestinationWidth { get; }
///
- /// Gets the target height.
+ /// Gets the destination height.
///
- public int TargetHeight { get; }
+ public int DestinationHeight { get; }
///
/// Gets the resize rectangle.
///
- public Rectangle TargetRectangle { get; }
+ public Rectangle DestinationRectangle { get; }
///
/// Gets a value indicating whether to compress or expand individual pixel color values on processing.
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
index 92c1b71fe..1a6b8030d 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
@@ -12,59 +12,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
/// Implements resizing of images using various resamplers.
///
- ///
- /// The original code has been adapted from .
- ///
/// The pixel format.
- internal class ResizeProcessor : TransformProcessor
+ internal partial class ResizeProcessor : TransformProcessor, IResamplingTransformImageProcessor
where TPixel : struct, IPixel
{
- private bool isDisposed;
- private readonly int targetWidth;
- private readonly int targetHeight;
+ private readonly int destinationWidth;
+ private readonly int destinationHeight;
private readonly IResampler resampler;
- private readonly Rectangle targetRectangle;
+ private readonly Rectangle destinationRectangle;
private readonly bool compand;
-
- // The following fields are not immutable but are optionally created on demand.
- private ResizeKernelMap horizontalKernelMap;
- private ResizeKernelMap verticalKernelMap;
+ private Image destination;
public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
- this.targetWidth = definition.TargetWidth;
- this.targetHeight = definition.TargetHeight;
- this.targetRectangle = definition.TargetRectangle;
+ this.destinationWidth = definition.DestinationWidth;
+ this.destinationHeight = definition.DestinationHeight;
+ this.destinationRectangle = definition.DestinationRectangle;
this.resampler = definition.Sampler;
this.compand = definition.Compand;
}
///
- protected override Size GetTargetSize() => new Size(this.targetWidth, this.targetHeight);
+ protected override Size GetDestinationSize() => new Size(this.destinationWidth, this.destinationHeight);
///
protected override void BeforeImageApply(Image destination)
{
- if (!(this.resampler is NearestNeighborResampler))
- {
- Image 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);
- }
+ this.destination = destination;
+ this.resampler.ApplyTransform(this);
base.BeforeImageApply(destination);
}
@@ -72,54 +48,143 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
protected override void OnFrameApply(ImageFrame source, ImageFrame destination)
{
- Rectangle sourceRectangle = this.SourceRectangle;
+ // Everything happens in BeforeImageApply.
+ }
+
+ public void ApplyTransform(in TResampler sampler)
+ where TResampler : struct, IResampler
+ {
Configuration configuration = this.Configuration;
+ Image source = this.Source;
+ Image destination = this.destination;
+ Rectangle sourceRectangle = this.SourceRectangle;
+ Rectangle destinationRectangle = this.destinationRectangle;
+ bool compand = this.compand;
// Handle resize dimensions identical to the original
if (source.Width == destination.Width
&& source.Height == destination.Height
- && sourceRectangle == this.targetRectangle)
+ && sourceRectangle == destinationRectangle)
{
- // The cloned will be blank here copy all the pixel data over
- source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup());
+ for (int i = 0; i < source.Frames.Count; i++)
+ {
+ ImageFrame sourceFrame = source.Frames[i];
+ ImageFrame destinationFrame = destination.Frames[i];
+
+ // The cloned will be blank here copy all the pixel data over
+ sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup());
+ }
+
return;
}
- int width = this.targetWidth;
- int height = this.targetHeight;
- var interest = Rectangle.Intersect(this.targetRectangle, new Rectangle(0, 0, width, height));
+ var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds());
+
+ if (sampler is NearestNeighborResampler)
+ {
+ for (int i = 0; i < source.Frames.Count; i++)
+ {
+ ImageFrame sourceFrame = source.Frames[i];
+ ImageFrame 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
- float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width;
- float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height;
+ ImageFrame sourceFrame = source.Frames[i];
+ ImageFrame destinationFrame = destination.Frames[i];
- var operation = new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination);
- ParallelRowIterator.IterateRows(
+ ApplyResizeFrameTransform(
configuration,
+ sourceFrame,
+ destinationFrame,
+ horizontalKernelMap,
+ verticalKernelMap,
+ sourceRectangle,
+ destinationRectangle,
interest,
- in operation);
-
- return;
+ compand);
}
+ }
+
+ private static void ApplyNNResizeFrameTransform(
+ Configuration configuration,
+ ImageFrame source,
+ ImageFrame 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 source,
+ ImageFrame destination,
+ ResizeKernelMap horizontalKernelMap,
+ ResizeKernelMap verticalKernelMap,
+ Rectangle sourceRectangle,
+ Rectangle destinationRectangle,
+ Rectangle interest,
+ bool compand)
+ {
PixelConversionModifiers conversionModifiers =
- PixelConversionModifiers.Premultiply.ApplyCompanding(this.compand);
+ PixelConversionModifiers.Premultiply.ApplyCompanding(compand);
BufferArea 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.
using (var worker = new ResizeWorker(
configuration,
sourceArea,
conversionModifiers,
- this.horizontalKernelMap,
- this.verticalKernelMap,
- width,
+ horizontalKernelMap,
+ verticalKernelMap,
+ destination.Width,
interest,
- this.targetRectangle.Location))
+ destinationRectangle.Location))
{
worker.Initialize();
@@ -128,27 +193,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
}
- ///
- 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 struct NNRowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle sourceBounds;
private readonly Rectangle destinationBounds;
@@ -158,7 +203,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame destination;
[MethodImpl(InliningOptions.ShortMethod)]
- public RowIntervalOperation(
+ public NNRowIntervalOperation(
Rectangle sourceBounds,
Rectangle destinationBounds,
float widthFactor,
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
index de339823e..5ba204135 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
@@ -6,7 +6,6 @@ using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -104,7 +103,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.tempColumnBuffer.Dispose();
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [MethodImpl(InliningOptions.ShortMethod)]
public Span GetColumnSpan(int x, int startY)
{
return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min);
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs
deleted file mode 100644
index a0d44cb7a..000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs
+++ /dev/null
@@ -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
-{
- ///
- /// Contains the methods required to calculate transform kernel convolution.
- ///
- internal class TransformKernelMap : IDisposable
- {
- private readonly Buffer2D yBuffer;
- private readonly Buffer2D xBuffer;
- private readonly Vector2 extents;
- private Vector4 maxSourceExtents;
- private readonly IResampler sampler;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The configuration.
- /// The source size.
- /// The destination size.
- /// The sampler.
- 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(yLength, destination.Height);
- this.xBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height);
-
- int maxX = source.Width - 1;
- int maxY = source.Height - 1;
- this.maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY);
- }
-
- ///
- /// Gets a reference to the first item of the y window.
- ///
- /// The reference to the first item of the window.
- [MethodImpl(InliningOptions.ShortMethod)]
- public ref float GetYStartReference(int y)
- => ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpan(y));
-
- ///
- /// Gets a reference to the first item of the x window.
- ///
- /// The reference to the first item of the window.
- [MethodImpl(InliningOptions.ShortMethod)]
- public ref float GetXStartReference(int y)
- => ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpan(y));
-
- public void Convolve(
- Vector2 transformedPoint,
- int column,
- ref float ySpanRef,
- ref float xSpanRef,
- Buffer2D sourcePixels,
- Span targetRow)
- where TPixel : struct, IPixel
- {
- // 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;
- }
-
- ///
- /// Calculated the normalized weights for the given point.
- ///
- /// The minimum sampling offset
- /// The maximum sampling offset
- /// The transformed point dimension
- /// The reference to the collection of weights
- [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();
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs
similarity index 96%
rename from src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
rename to src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs
index e0fb55438..0760d2e3e 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs
@@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
///
/// Contains utility methods for working with transforms.
///
- internal static class TransformUtils
+ internal static class TransformUtilities
{
///
/// Applies the projective transform against the given coordinates flattened into the 2D space.
@@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// The amount of rotation, in degrees.
/// The source image size.
/// The .
+ [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
/// The amount of rotation, in radians.
/// The source image size.
/// The .
+ [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
/// The Y angle, in degrees.
/// The source image size.
/// The .
+ [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
/// The Y angle, in radians.
/// The source image size.
/// The .
+ [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
/// The source image bounds.
/// The transformation matrix.
/// The
+ [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
/// An enumeration that indicates on which corners to taper the rectangle.
/// The amount to taper.
/// The
+ [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
///
/// The .
///
+ [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
///
/// The .
///
+ [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
///
/// The .
///
+ [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
diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
index 0ff693d81..ef44dc16d 100644
--- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
+++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
@@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing
/// The amount to taper.
/// The .
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));
///
/// Appends a matrix that performs a tapering projective transform.
@@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing
/// The amount to taper.
/// The .
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));
///
/// Prepends a centered rotation matrix using the given rotation in degrees.
@@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing
/// The amount of rotation, in radians.
/// The .
public ProjectiveTransformBuilder PrependRotationRadians(float radians)
- => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size)));
+ => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size)));
///
/// Prepends a centered rotation matrix using the given rotation in degrees at the given origin.
@@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing
/// The amount of rotation, in radians.
/// The .
public ProjectiveTransformBuilder AppendRotationRadians(float radians)
- => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size)));
+ => this.Append(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size)));
///
/// Appends a centered rotation matrix using the given rotation in degrees at the given origin.
@@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Processing
/// The Y angle, in radians.
/// The .
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)));
///
/// Prepends a skew matrix using the given angles in degrees at the given origin.
@@ -205,7 +205,7 @@ namespace SixLabors.ImageSharp.Processing
/// The Y angle, in radians.
/// The .
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)));
///
/// Appends a skew matrix using the given angles in degrees at the given origin.
diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs
index 096167eb9..e53661c73 100644
--- a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs
+++ b/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 ####
//
// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs
index e16e376fe..0610079fe 100644
--- a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs
+++ b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs
@@ -23,27 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
}
}
-/*
- Nov 7 2018
-BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763
-Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores
-.NET Core SDK = 2.1.403
-
- [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
- Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0
- Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
-
-LaunchCount=1 TargetCount=3 WarmupCount=3
-
- #### BEFORE ####:
- Method | Runtime | Mean | Error | StdDev | Allocated |
---------- |-------- |---------:|----------:|----------:|----------:|
- DoRotate | Clr | 85.19 ms | 13.379 ms | 0.7560 ms | 6 KB |
- DoRotate | Core | 53.51 ms | 9.512 ms | 0.5375 ms | 4.29 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 |
- */
+// #### 21th February 2020 ####
+//
+// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
+// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
+// .NET Core SDK = 3.1.101
+//
+// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
+// Job-HOGSNT : .NET Framework 4.8 (4.8.4121.0), X64 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
+//
+// IterationCount=3 LaunchCount=1 WarmupCount=3
+//
+// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
+// |--------- |-------------- |---------:|---------:|---------:|------:|------:|------:|----------:|
+// | DoRotate | .NET 4.7.2 | 28.77 ms | 3.304 ms | 0.181 ms | - | - | - | 6.5 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 |
diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs
index 0ad27861b..7b8ec83a5 100644
--- a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs
+++ b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using BenchmarkDotNet.Attributes;
-
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@@ -24,27 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
}
}
-/*
- Nov 7 2018
-BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763
-Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores
-.NET Core SDK = 2.1.403
-
- [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
- Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0
- Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
-
-LaunchCount=1 TargetCount=3 WarmupCount=3
-
- #### BEFORE ####:
-Method | Runtime | Mean | Error | StdDev | Allocated |
-------- |-------- |---------:|---------:|----------:|----------:|
- DoSkew | Clr | 78.14 ms | 8.383 ms | 0.4736 ms | 6 KB |
- DoSkew | Core | 44.22 ms | 4.109 ms | 0.2322 ms | 4.28 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 |
- */
+// #### 21th February 2020 ####
+//
+// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
+// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
+// .NET Core SDK = 3.1.101
+//
+// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
+// Job-VKKTMF : .NET Framework 4.8 (4.8.4121.0), X64 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
+//
+// IterationCount=3 LaunchCount=1 WarmupCount=3
+//
+// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
+// |------- |-------------- |---------:|----------:|---------:|------:|------:|------:|----------:|
+// | DoSkew | .NET 4.7.2 | 24.60 ms | 33.971 ms | 1.862 ms | - | - | - | 6.5 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 |
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
index beb7ebc9c..3d08cf1a4 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs
+++ b/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 static ReferenceKernelMap Calculate(IResampler sampler, int destinationSize, int sourceSize, bool normalize = true)
+ public static ReferenceKernelMap Calculate(in TResampler sampler, int destinationSize, int sourceSize, bool normalize = true)
+ where TResampler : struct, IResampler
{
double ratio = (double)sourceSize / destinationSize;
double scale = ratio;
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
index 08745d570..8dbc05655 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
@@ -25,59 +25,60 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
///
/// resamplerName, srcSize, destSize
///
- public static readonly TheoryData KernelMapData = new TheoryData
+ public static readonly TheoryData KernelMapData
+ = new TheoryData
{
- { nameof(KnownResamplers.Bicubic), 15, 10 },
- { nameof(KnownResamplers.Bicubic), 10, 15 },
- { nameof(KnownResamplers.Bicubic), 20, 20 },
- { nameof(KnownResamplers.Bicubic), 50, 40 },
- { nameof(KnownResamplers.Bicubic), 40, 50 },
- { nameof(KnownResamplers.Bicubic), 500, 200 },
- { nameof(KnownResamplers.Bicubic), 200, 500 },
- { nameof(KnownResamplers.Bicubic), 3032, 400 },
- { nameof(KnownResamplers.Bicubic), 10, 25 },
- { nameof(KnownResamplers.Lanczos3), 16, 12 },
- { nameof(KnownResamplers.Lanczos3), 12, 16 },
- { nameof(KnownResamplers.Lanczos3), 12, 9 },
- { nameof(KnownResamplers.Lanczos3), 9, 12 },
- { nameof(KnownResamplers.Lanczos3), 6, 8 },
- { nameof(KnownResamplers.Lanczos3), 8, 6 },
- { nameof(KnownResamplers.Lanczos3), 20, 12 },
- { nameof(KnownResamplers.Lanczos3), 5, 25 },
- { nameof(KnownResamplers.Lanczos3), 5, 50 },
- { nameof(KnownResamplers.Lanczos3), 25, 5 },
- { nameof(KnownResamplers.Lanczos3), 50, 5 },
- { nameof(KnownResamplers.Lanczos3), 49, 5 },
- { nameof(KnownResamplers.Lanczos3), 31, 5 },
- { nameof(KnownResamplers.Lanczos8), 500, 200 },
- { nameof(KnownResamplers.Lanczos8), 100, 10 },
- { nameof(KnownResamplers.Lanczos8), 100, 80 },
- { nameof(KnownResamplers.Lanczos8), 10, 100 },
+ { KnownResamplers.Bicubic, 15, 10 },
+ { KnownResamplers.Bicubic, 10, 15 },
+ { KnownResamplers.Bicubic, 20, 20 },
+ { KnownResamplers.Bicubic, 50, 40 },
+ { KnownResamplers.Bicubic, 40, 50 },
+ { KnownResamplers.Bicubic, 500, 200 },
+ { KnownResamplers.Bicubic, 200, 500 },
+ { KnownResamplers.Bicubic, 3032, 400 },
+ { KnownResamplers.Bicubic, 10, 25 },
+ { KnownResamplers.Lanczos3, 16, 12 },
+ { KnownResamplers.Lanczos3, 12, 16 },
+ { KnownResamplers.Lanczos3, 12, 9 },
+ { KnownResamplers.Lanczos3, 9, 12 },
+ { KnownResamplers.Lanczos3, 6, 8 },
+ { KnownResamplers.Lanczos3, 8, 6 },
+ { KnownResamplers.Lanczos3, 20, 12 },
+ { KnownResamplers.Lanczos3, 5, 25 },
+ { KnownResamplers.Lanczos3, 5, 50 },
+ { KnownResamplers.Lanczos3, 25, 5 },
+ { KnownResamplers.Lanczos3, 50, 5 },
+ { KnownResamplers.Lanczos3, 49, 5 },
+ { KnownResamplers.Lanczos3, 31, 5 },
+ { KnownResamplers.Lanczos8, 500, 200 },
+ { KnownResamplers.Lanczos8, 100, 10 },
+ { KnownResamplers.Lanczos8, 100, 80 },
+ { KnownResamplers.Lanczos8, 10, 100 },
// Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5:
- { nameof(KnownResamplers.Box), 378, 149 },
- { nameof(KnownResamplers.Box), 349, 174 },
+ { KnownResamplers.Box, 378, 149 },
+ { KnownResamplers.Box, 349, 174 },
// Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData
- { nameof(KnownResamplers.Box), 201, 100 },
- { nameof(KnownResamplers.Box), 199, 99 },
- { nameof(KnownResamplers.Box), 10, 299 },
- { nameof(KnownResamplers.Box), 299, 10 },
- { nameof(KnownResamplers.Box), 301, 300 },
- { nameof(KnownResamplers.Box), 1180, 480 },
- { nameof(KnownResamplers.Lanczos2), 3264, 3032 },
- { nameof(KnownResamplers.Bicubic), 1280, 2240 },
- { nameof(KnownResamplers.Bicubic), 1920, 1680 },
- { nameof(KnownResamplers.Bicubic), 3072, 2240 },
- { nameof(KnownResamplers.Welch), 300, 2008 },
+ { KnownResamplers.Box, 201, 100 },
+ { KnownResamplers.Box, 199, 99 },
+ { KnownResamplers.Box, 10, 299 },
+ { KnownResamplers.Box, 299, 10 },
+ { KnownResamplers.Box, 301, 300 },
+ { KnownResamplers.Box, 1180, 480 },
+ { KnownResamplers.Lanczos2, 3264, 3032 },
+ { KnownResamplers.Bicubic, 1280, 2240 },
+ { KnownResamplers.Bicubic, 1920, 1680 },
+ { KnownResamplers.Bicubic, 3072, 2240 },
+ { KnownResamplers.Welch, 300, 2008 },
// ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData
- { nameof(KnownResamplers.Bicubic), 10, 50 },
- { nameof(KnownResamplers.Bicubic), 49, 301 },
- { nameof(KnownResamplers.Bicubic), 301, 49 },
- { nameof(KnownResamplers.Bicubic), 1680, 1200 },
- { nameof(KnownResamplers.Box), 13, 299 },
- { nameof(KnownResamplers.Lanczos5), 3032, 600 },
+ { KnownResamplers.Bicubic, 10, 50 },
+ { KnownResamplers.Bicubic, 49, 301 },
+ { KnownResamplers.Bicubic, 301, 49 },
+ { KnownResamplers.Bicubic, 1680, 1200 },
+ { KnownResamplers.Box, 13, 299 },
+ { KnownResamplers.Lanczos5, 3032, 600 },
};
public static TheoryData GeneratedImageResizeData =
@@ -85,20 +86,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
[Theory(Skip = "Only for debugging and development")]
[MemberData(nameof(KernelMapData))]
- public void PrintNonNormalizedKernelMap(string resamplerName, int srcSize, int destSize)
+ public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize)
+ where TResampler : struct, IResampler
{
- IResampler resampler = TestUtils.GetResampler(resamplerName);
-
- var kernelMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize, false);
+ var kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false);
this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n");
}
[Theory]
[MemberData(nameof(KernelMapData))]
- public void KernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize)
+ public void KernelMapContentIsCorrect(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.
@@ -113,12 +114,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
}
#endif
- private void VerifyKernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize)
+ private void VerifyKernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize)
+ where TResampler : struct, IResampler
{
- IResampler resampler = TestUtils.GetResampler(resamplerName);
-
- var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize);
- var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator);
+ var referenceMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize);
+ var kernelMap = ResizeKernelMap.Calculate(in resampler, destSize, srcSize, Configuration.Default.MemoryAllocator);
#if DEBUG
this.Output.WriteLine(kernelMap.Info);
@@ -153,11 +153,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
}
}
- private static string PrintKernelMap(ResizeKernelMap kernelMap) =>
- PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i));
+ private static string PrintKernelMap(ResizeKernelMap kernelMap)
+ => PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i));
- private static string PrintKernelMap(ReferenceKernelMap kernelMap) =>
- PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i));
+ private static string PrintKernelMap(ReferenceKernelMap kernelMap)
+ => PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i));
private static string PrintKernelMap(
TKernelMap kernelMap,
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
index 2cbffef47..7086bfeb3 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
@@ -121,8 +121,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
configuration.MemoryAllocator = allocator;
configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes;
- var verticalKernelMap = ResizeKernelMap.Calculate(
- KnownResamplers.Bicubic,
+ var verticalKernelMap = ResizeKernelMap.Calculate(
+ default,
destSize.Height,
image0.Height,
Configuration.Default.MemoryAllocator);
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs
index 33da33c71..db1e76ae5 100644
--- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs
+++ b/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.
using SixLabors.ImageSharp.Processing;
@@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Pad(width, height);
ResizeProcessor resizeProcessor = this.Verify();
- Assert.Equal(width, resizeProcessor.TargetWidth);
- Assert.Equal(height, resizeProcessor.TargetHeight);
+ Assert.Equal(width, resizeProcessor.DestinationWidth);
+ Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs
index f87e17e06..e7b92b7b3 100644
--- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs
@@ -17,8 +17,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height);
ResizeProcessor resizeProcessor = this.Verify();
- Assert.Equal(width, resizeProcessor.TargetWidth);
- Assert.Equal(height, resizeProcessor.TargetHeight);
+ Assert.Equal(width, resizeProcessor.DestinationWidth);
+ Assert.Equal(height, resizeProcessor.DestinationHeight);
}
[Fact]
@@ -30,8 +30,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height, sampler);
ResizeProcessor resizeProcessor = this.Verify();
- Assert.Equal(width, resizeProcessor.TargetWidth);
- Assert.Equal(height, resizeProcessor.TargetHeight);
+ Assert.Equal(width, resizeProcessor.DestinationWidth);
+ Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
}
@@ -47,8 +47,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height, sampler, compand);
ResizeProcessor resizeProcessor = this.Verify();
- Assert.Equal(width, resizeProcessor.TargetWidth);
- Assert.Equal(height, resizeProcessor.TargetHeight);
+ Assert.Equal(width, resizeProcessor.DestinationWidth);
+ Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand);
}
@@ -73,8 +73,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(resizeOptions);
ResizeProcessor resizeProcessor = this.Verify();
- Assert.Equal(width, resizeProcessor.TargetWidth);
- Assert.Equal(height, resizeProcessor.TargetHeight);
+ Assert.Equal(width, resizeProcessor.DestinationWidth);
+ Assert.Equal(height, resizeProcessor.DestinationHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand);
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs
index e1b6e18a7..21359799e 100644
--- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs
+++ b/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);
diff --git a/tests/Images/External b/tests/Images/External
index f9b4bfe42..f8a76fd3a 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit f9b4bfe42cacb3eefab02ada92ac771a9b93c080
+Subproject commit f8a76fd3a900b90c98df67ac896574383a4d09f3