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