diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index 90e00924ad..dde7beb3e9 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 6305649557..ee8f3854e8 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 621215b280..6c73513c87 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; } = default(CatmullRomResampler); /// /// 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; } = default(HermiteResampler); /// /// 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; } = default(Lanczos2Resampler); /// /// 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; } = default(Lanczos3Resampler); /// /// 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; } = default(Lanczos5Resampler); /// /// 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; } = default(Lanczos8Resampler); /// /// 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; } = default(MitchellNetravaliResampler); /// /// 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; } = default(RobidouxResampler); /// /// Gets the Robidoux Sharp sampler. A sharpened form of the sampler /// - public static IResampler RobidouxSharp { get; } = new RobidouxSharpResampler(); + public static IResampler RobidouxSharp { get; } = default(RobidouxSharpResampler); /// /// Gets the Spline sampler. A seperable cubic algorithm similar to but yielding smoother results. /// - public static IResampler Spline { get; } = new SplineResampler(); + public static IResampler Spline { get; } = default(SplineResampler); /// /// 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/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 0d9055f340..130cc1bf05 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -1,11 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -40,149 +36,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// 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.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return; - } - - int width = this.targetSize.Width; - var targetBounds = new Rectangle(Point.Empty, this.targetSize); - Configuration configuration = this.Configuration; - - // Convert from screen to world space. - Matrix3x2.Invert(this.transformMatrix, out Matrix3x2 matrix); - - if (this.resampler is NearestNeighborResampler) - { - var nnOperation = new NearestNeighborRowIntervalOperation(this.SourceRectangle, ref matrix, width, source, destination); - ParallelRowIterator.IterateRows( - configuration, - targetBounds, - in nnOperation); - - return; - } - - using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - - var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination); - ParallelRowIterator.IterateRows( - 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); - } - } - } + => this.resampler.ApplyAffineTransform(this.Configuration, source, destination, this.transformMatrix); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs index 6db03d5b41..fb095b70ab 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs @@ -1,6 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -21,5 +24,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The /// float GetValue(float x); + + /// + /// Applies an affine transformation upon an image. + /// + /// The pixel format. + /// The configuration. + /// The source image frame. + /// The destination image frame. + /// The transform matrix. + void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel; + + /// + /// Applies a projective transformation upon an image. + /// + /// The pixel format. + /// The configuration. + /// The source image frame. + /// The destination image frame. + /// The transform matrix. + void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index da071e3f22..50315ac95c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -1,11 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -40,145 +36,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// 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(Matrix4x4.Identity)) - { - // The clone will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return; - } - - int width = this.targetSize.Width; - var targetBounds = new Rectangle(Point.Empty, this.targetSize); - Configuration configuration = this.Configuration; - - // Convert from screen to world space. - Matrix4x4.Invert(this.transformMatrix, out Matrix4x4 matrix); - - if (this.resampler is NearestNeighborResampler) - { - Rectangle sourceBounds = this.SourceRectangle; - - var nnOperation = new NearestNeighborRowIntervalOperation(sourceBounds, ref matrix, width, source, destination); - ParallelRowIterator.IterateRows( - configuration, - targetBounds, - in nnOperation); - - return; - } - - using var kernelMap = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - - var operation = new RowIntervalOperation(configuration, kernelMap, ref matrix, width, source, destination); - ParallelRowIterator.IterateRows( - configuration, - targetBounds, - in operation); - } - - private readonly struct NearestNeighborRowIntervalOperation : IRowIntervalOperation - { - 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, - 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++) - { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); - int px = (int)MathF.Round(point.X); - int py = (int)MathF.Round(point.Y); - - if (this.bounds.Contains(px, py)) - { - destRow[x] = this.source[px, py]; - } - } - } - } - } - - private readonly struct RowIntervalOperation : IRowIntervalOperation - { - private readonly Configuration configuration; - private readonly TransformKernelMap kernelMap; - private readonly Matrix4x4 matrix; - private readonly int maxX; - private readonly ImageFrame source; - private readonly ImageFrame destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - Configuration configuration, - TransformKernelMap kernelMap, - ref Matrix4x4 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. - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); - this.kernelMap.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - this.source.PixelBuffer, - span); - } - - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - targetRowSpan); - } - } - } + => this.resampler.ApplyProjectiveTransform(this.Configuration, source, destination, this.transformMatrix); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs new file mode 100644 index 0000000000..96fcc49f7b --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs @@ -0,0 +1,263 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Extensions for . + /// + public static partial class ResamplerExtensions + { + private readonly struct NNAffineOperation : IRowIntervalOperation + where TPixel : struct, IPixel + { + 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 = destination.Bounds(); + this.matrix = matrix; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) + { + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); + + if (this.bounds.Contains(px, py)) + { + destRow[x] = this.source[px, py]; + } + } + } + } + } + + private readonly struct NNProjectiveOperation : IRowIntervalOperation + where TPixel : struct, IPixel + { + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Rectangle bounds; + private readonly Matrix4x4 matrix; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNProjectiveOperation( + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + { + this.source = source; + this.destination = destination; + this.bounds = destination.Bounds(); + this.matrix = matrix; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) + { + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); + + if (this.bounds.Contains(px, py)) + { + destRow[x] = this.source[px, py]; + } + } + } + } + } + + private readonly struct AffineOperation : IRowIntervalOperation + where TResampler : unmanaged, IResampler + where TPixel : struct, IPixel + { + 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); + 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)); + } + } + } + + private readonly struct ProjectiveOperation : IRowIntervalOperation + where TResampler : unmanaged, IResampler + where TPixel : struct, IPixel + { + 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 Matrix4x4 matrix; + private readonly Vector2 radialExtents; + private readonly Vector4 maxSourceExtents; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ProjectiveOperation( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Buffer2D yKernelBuffer, + Buffer2D xKernelBuffer, + in TResampler sampler, + Matrix4x4 matrix, + Vector2 radialExtents, + Vector4 maxSourceExtents) + { + this.configuration = configuration; + this.source = source; + this.destination = destination; + this.yKernelBuffer = yKernelBuffer; + this.xKernelBuffer = xKernelBuffer; + this.sampler = sampler; + this.matrix = matrix; + this.radialExtents = radialExtents; + this.maxSourceExtents = maxSourceExtents; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Span 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. + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); + Convolve( + in this.sampler, + point, + sourceBuffer, + span, + x, + ref yKernelSpanRef, + ref xKernelSpanRef, + this.radialExtents, + this.maxSourceExtents); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + this.destination.GetPixelRowSpan(y)); + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs new file mode 100644 index 0000000000..674b7f4231 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs @@ -0,0 +1,249 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Extensions for . + /// + public static partial class ResamplerExtensions + { + /// + /// Applies an affine transformation upon an image. + /// + /// The type of sampler. + /// The pixel format. + /// The configuration. + /// The pixel sampler. + /// The source image frame. + /// The destination image frame. + /// The transform matrix. + public static void ApplyAffineTransform( + Configuration configuration, + in TResampler sampler, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TResampler : unmanaged, IResampler + where TPixel : struct, IPixel + { + // Handle transforms that result in output identical to the original. + if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return; + } + + // Convert from screen to world space. + Matrix3x2.Invert(matrix, out matrix); + + if (sampler is NearestNeighborResampler) + { + var nnOperation = new NNAffineOperation(source, destination, matrix); + ParallelRowIterator.IterateRows( + configuration, + destination.Bounds(), + in nnOperation); + + return; + } + + int yRadius = GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = GetSamplingRadius(in sampler, source.Width, destination.Width); + var radialExtents = new Vector2(xRadius, yRadius); + int yLength = (yRadius * 2) + 1; + int xLength = (xRadius * 2) + 1; + + // We use 2D buffers so that we can access the weight spans per row in parallel. + using Buffer2D 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); + } + + /// + /// Applies a projective transformation upon an image. + /// + /// The type of sampler. + /// The pixel format. + /// The configuration. + /// The pixel sampler. + /// The source image frame. + /// The destination image frame. + /// The transform matrix. + public static void ApplyProjectiveTransform( + Configuration configuration, + in TResampler sampler, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TResampler : unmanaged, IResampler + where TPixel : struct, IPixel + { + // Handle transforms that result in output identical to the original. + if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + return; + } + + // Convert from screen to world space. + Matrix4x4.Invert(matrix, out matrix); + + if (sampler is NearestNeighborResampler) + { + var nnOperation = new NNProjectiveOperation(source, destination, matrix); + ParallelRowIterator.IterateRows( + configuration, + destination.Bounds(), + in nnOperation); + + return; + } + + int yRadius = GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = GetSamplingRadius(in sampler, source.Width, destination.Width); + var radialExtents = new Vector2(xRadius, yRadius); + int yLength = (yRadius * 2) + 1; + int xLength = (xRadius * 2) + 1; + + // We use 2D buffers so that we can access the weight spans per row in parallel. + using Buffer2D 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 ProjectiveOperation( + configuration, + source, + destination, + yKernelBuffer, + xKernelBuffer, + in sampler, + matrix, + radialExtents, + maxSourceExtents); + + ParallelRowIterator.IterateRows, Vector4>( + configuration, + destination.Bounds(), + in operation); + } + + [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 : unmanaged, 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 : unmanaged, IResampler + { + float sum = 0; + for (int x = 0, i = min; i <= max; i++, x++) + { + float weight = sampler.GetValue(i - point); + sum += weight; + Unsafe.Add(ref weightsRef, x) = weight; + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetSamplingRadius(in TResampler sampler, int sourceSize, int destinationSize) + where TResampler : unmanaged, IResampler + { + double scale = sourceSize / destinationSize; + if (scale < 1) + { + scale = 1; + } + + return (int)Math.Ceiling(scale * sampler.Radius); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs index 199563bc7e..ea68715753 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs @@ -1,6 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +12,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 +26,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms x = -x; } - float result = 0; - // Given the coefficient "a" as -0.5F. if (x <= 1F) { // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (x * x)) + 1; - result = (((1.5F * x) - 2.5F) * x * x) + 1; + return (((1.5F * x) - 2.5F) * x * x) + 1; } else if (x < 2F) { // Below simplified result = (a * (x * x * x)) - ((5F * a) * (x * x)) + ((8F * a) * x) - (4F * a); - result = (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; + return (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; } - return result; + return 0; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 0667226d9c..49b53378f9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// 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 +27,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 index 8995d2d8a8..5a2992595d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs @@ -1,6 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -9,12 +13,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// scale image enlargements that a 'Lagrange' filter can produce. /// /// - public class CatmullRomResampler : IResampler + public readonly struct CatmullRomResampler : IResampler { /// public float Radius => 2; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { const float B = 0; @@ -22,5 +27,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs index 18c3fda7c0..80aa69acd6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs @@ -1,6 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. /// /// - public class HermiteResampler : IResampler + public readonly struct HermiteResampler : IResampler { /// public float Radius => 2; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { const float B = 0F; @@ -21,5 +26,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 index 2294696de4..3228a709d2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs @@ -1,6 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Wikipedia /// with a radius of 2 pixels. /// - public class Lanczos2Resampler : IResampler + public readonly struct Lanczos2Resampler : IResampler { /// public float Radius => 2; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -28,5 +33,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 index 95fb206a96..a9388575be 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs @@ -1,6 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Wikipedia /// with a radius of 3 pixels. /// - public class Lanczos3Resampler : IResampler + public readonly struct Lanczos3Resampler : IResampler { /// public float Radius => 3; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -28,5 +33,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 index c99ed1e855..7662f26160 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs @@ -1,6 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Wikipedia /// with a radius of 5 pixels. /// - public class Lanczos5Resampler : IResampler + public readonly struct Lanczos5Resampler : IResampler { /// public float Radius => 5; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -28,5 +33,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 index 4efdb882b0..e886f41db9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs @@ -1,6 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Wikipedia /// with a radius of 8 pixels. /// - public class Lanczos8Resampler : IResampler + public readonly struct Lanczos8Resampler : IResampler { /// public float Radius => 8; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -28,5 +33,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs index d4ba954f20..ef97be92b5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs @@ -1,13 +1,17 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the mitchell algorithm as described on /// Wikipedia /// - public class MitchellNetravaliResampler : IResampler + public readonly struct MitchellNetravaliResampler : IResampler { /// public float Radius => 2; @@ -20,5 +24,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 1f12334f4f..e4cec5f4d5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs @@ -1,21 +1,51 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// 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 ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 index 51938566c8..6d9e7641e0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the Robidoux algorithm. /// /// - public class RobidouxResampler : IResampler + public readonly struct RobidouxResampler : IResampler { /// public float Radius => 2; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { const float B = 0.37821575509399867F; @@ -20,5 +25,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 index 015b7f0af3..eaf5fa6e80 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the Robidoux Sharp algorithm. /// /// - public class RobidouxSharpResampler : IResampler + public readonly struct RobidouxSharpResampler : IResampler { /// public float Radius => 2; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { const float B = 0.2620145123990142F; @@ -20,5 +25,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 index df6c2a338f..d2608b2faa 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the spline algorithm. /// /// - public class SplineResampler : IResampler + public readonly struct SplineResampler : IResampler { /// public float Radius => 2; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { const float B = 1F; @@ -20,5 +25,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 57d1fa11dc..b403621241 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs @@ -1,6 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +12,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, /// so that one can calculate and assign appropriate intensity values to pixels. /// - 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 +33,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ 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 edce5fcf9e..8a92ea7c00 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs @@ -1,18 +1,23 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// 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 +32,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyAffineTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyAffineTransform( + configuration, + in this, + source, + destination, + matrix); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyProjectiveTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + where TPixel : struct, IPixel => ResamplerExtensions.ApplyProjectiveTransform( + configuration, + in this, + source, + destination, + matrix); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index aae66e9eac..b53e7b5c05 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/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/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index 4d07333345..1bcfa5fd28 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/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/TransformKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs deleted file mode 100644 index a0d44cb7a1..0000000000 --- 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 e0fb554385..0760d2e3e7 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 0ff693d81c..ef44dc16d0 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.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index e1b6e18a7a..21359799e7 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);