diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs new file mode 100644 index 0000000000..1d215d2860 --- /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/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor.cs similarity index 96% rename from src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor.cs index d0000edcf9..fec41dbffe 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor.cs @@ -19,6 +19,8 @@ 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.DestinationSize = targetDimensions; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.Transforms.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.Transforms.cs new file mode 100644 index 0000000000..3190857dd0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.Transforms.cs @@ -0,0 +1,209 @@ +// 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 +{ + /// + /// Contains the application code for performing an affine transform. + /// + internal partial class AffineTransformProcessor + { + /// + /// Applies an affine transformation upon an image. + /// + /// The type of sampler. + /// 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 : struct, IResampler + { + // 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 = AutomorphicTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = AutomorphicTransformUtilities.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); + AutomorphicTransformUtilities.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/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.cs similarity index 72% rename from src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.cs index dddeba33fa..78310707c8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.cs @@ -10,12 +10,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Provides the base methods to perform affine transforms on an image. /// /// The pixel format. - internal class AffineTransformProcessor : TransformProcessor + internal partial class AffineTransformProcessor : TransformProcessor, IResamplingImageProcessor 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. @@ -36,6 +38,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - => this.resampler.ApplyAffineTransform(this.Configuration, source, destination, this.transformMatrix); + { + this.source = source; + this.destination = destination; + this.resampler.ApplyTransform(this); + } + + /// + public void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler + => ApplyAffineTransform( + this.Configuration, + in sampler, + this.source, + this.destination, + this.transformMatrix); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutoOrientProcessor.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutoOrientProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutoOrientProcessor{TPixel}.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutoOrientProcessor{TPixel}.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutomorphicTransformUtilities.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutomorphicTransformUtilities.cs new file mode 100644 index 0000000000..b2283af010 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutomorphicTransformUtilities.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 AutomorphicTransformUtilities + { + [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/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/FlipProcessor.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/FlipProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/FlipProcessor{TPixel}.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/FlipProcessor{TPixel}.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor.cs similarity index 96% rename from src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor.cs index 6f17c11450..f716ba701e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor.cs @@ -19,6 +19,8 @@ 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.DestinationSize = targetDimensions; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.Transforms.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.Transforms.cs new file mode 100644 index 0000000000..c824bebaec --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.Transforms.cs @@ -0,0 +1,209 @@ +// 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 +{ + /// + /// Contains the application code for performing a projective transform. + /// + internal partial class ProjectiveTransformProcessor + { + /// + /// Applies a projective transformation upon an image. + /// + /// The type of sampler. + /// 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 : struct, IResampler + { + // 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.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + 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 = AutomorphicTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = AutomorphicTransformUtilities.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); + } + + 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; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNProjectiveOperation( + ImageFrame source, + ImageFrame destination, + Matrix4x4 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++) + { + 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 ProjectiveOperation : 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 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); + AutomorphicTransformUtilities.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/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.cs similarity index 72% rename from src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.cs index 6ab1e1358d..8954d826f5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.cs @@ -10,12 +10,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 partial class ProjectiveTransformProcessor : TransformProcessor, IResamplingImageProcessor where TPixel : struct, IPixel { 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. @@ -36,6 +38,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - => this.resampler.ApplyProjectiveTransform(this.Configuration, source, destination, this.transformMatrix); + { + this.source = source; + this.destination = destination; + this.resampler.ApplyTransform(this); + } + + /// + public void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler + => ApplyProjectiveTransform( + this.Configuration, + in sampler, + this.source, + this.destination, + this.transformMatrix); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/RotateProcessor.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/RotateProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/RotateProcessor{TPixel}.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/RotateProcessor{TPixel}.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/SkewProcessor.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Automorphic/SkewProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs index c7557461a8..616872f2ab 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs @@ -1,7 +1,6 @@ // 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 @@ -26,52 +25,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float GetValue(float x); /// - /// Applies an resizing transformation upon an image. + /// Applies a transformation upon an image. /// /// The pixel format. - /// The configuration. - /// The source image. - /// The destination image. - /// The source bounds. - /// The target location. - /// Whether to compress or expand individual pixel color values on processing. - void ApplyResizeTransform( - Configuration configuration, - Image source, - Image destination, - Rectangle sourceRectangle, - Rectangle targetRectangle, - bool compand) - where TPixel : struct, IPixel; - - /// - /// 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) + /// The transforming image processor. + void ApplyTransform(IResamplingImageProcessor processor) where TPixel : struct, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResamplingImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/IResamplingImageProcessor{TPixel}.cs new file mode 100644 index 0000000000..cfa2df641d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/IResamplingImageProcessor{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 a resampling transforms. + /// + /// The pixel format. + public interface IResamplingImageProcessor : 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/ResamplerExtensions.Operations.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs deleted file mode 100644 index ec2aef9c50..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs +++ /dev/null @@ -1,263 +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.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 = 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 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 = 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++) - { - 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 deleted file mode 100644 index 245adb2383..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs +++ /dev/null @@ -1,249 +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 -{ - /// - /// 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.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 = 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.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); - 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 5f9669f6f8..2992bbf5ac 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs @@ -1,7 +1,6 @@ // 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; @@ -43,48 +42,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyResizeTransform( - Configuration configuration, - Image source, - Image destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - bool compand) - where TPixel : struct, IPixel => ResamplerExtensions.ApplyResizeTransform( - configuration, - in this, - source, - destination, - sourceRectangle, - destinationRectangle, - compand); - - /// - [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); + public void ApplyTransform(IResamplingImageProcessor processor) + where TPixel : struct, IPixel + => processor.ApplyTransform(in this); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs index ecaa28c804..98a789e342 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs @@ -1,7 +1,6 @@ // 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; @@ -30,48 +29,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyResizeTransform( - Configuration configuration, - Image source, - Image destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - bool compand) - where TPixel : struct, IPixel => ResamplerExtensions.ApplyResizeTransform( - configuration, - in this, - source, - destination, - sourceRectangle, - destinationRectangle, - compand); - - /// - [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); + public void ApplyTransform(IResamplingImageProcessor processor) + where TPixel : struct, IPixel + => processor.ApplyTransform(in this); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs index a8f3f0b63a..a6fba1b33d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs @@ -1,7 +1,6 @@ // 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; @@ -106,48 +105,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyResizeTransform( - Configuration configuration, - Image source, - Image destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - bool compand) - where TPixel : struct, IPixel => ResamplerExtensions.ApplyResizeTransform( - configuration, - in this, - source, - destination, - sourceRectangle, - destinationRectangle, - compand); - - /// - [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); + public void ApplyTransform(IResamplingImageProcessor processor) + where TPixel : struct, IPixel + => processor.ApplyTransform(in this); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs index 4ed2d541c2..90d60e1b08 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs @@ -1,7 +1,6 @@ // 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; @@ -62,48 +61,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyResizeTransform( - Configuration configuration, - Image source, - Image destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - bool compand) - where TPixel : struct, IPixel => ResamplerExtensions.ApplyResizeTransform( - configuration, - in this, - source, - destination, - sourceRectangle, - destinationRectangle, - compand); - - /// - [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); + public void ApplyTransform(IResamplingImageProcessor processor) + where TPixel : struct, IPixel + => processor.ApplyTransform(in this); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs index 94b0b0405f..20f0a9fb85 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs @@ -1,7 +1,6 @@ // 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; @@ -22,48 +21,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyResizeTransform( - Configuration configuration, - Image source, - Image destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - bool compand) - where TPixel : struct, IPixel => ResamplerExtensions.ApplyResizeTransform( - configuration, - in this, - source, - destination, - sourceRectangle, - destinationRectangle, - compand); - - /// - [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); + public void ApplyTransform(IResamplingImageProcessor processor) + where TPixel : struct, IPixel + => processor.ApplyTransform(in this); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs index c8409e1859..9cf9ae66af 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs @@ -1,7 +1,6 @@ // 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; @@ -36,48 +35,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyResizeTransform( - Configuration configuration, - Image source, - Image destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - bool compand) - where TPixel : struct, IPixel => ResamplerExtensions.ApplyResizeTransform( - configuration, - in this, - source, - destination, - sourceRectangle, - destinationRectangle, - compand); - - /// - [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); + public void ApplyTransform(IResamplingImageProcessor processor) + where TPixel : struct, IPixel + => processor.ApplyTransform(in this); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs index 673cbd5d74..a162c7411f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs @@ -1,7 +1,6 @@ // 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; @@ -35,48 +34,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyResizeTransform( - Configuration configuration, - Image source, - Image destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - bool compand) - where TPixel : struct, IPixel => ResamplerExtensions.ApplyResizeTransform( - configuration, - in this, - source, - destination, - sourceRectangle, - destinationRectangle, - compand); - - /// - [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); + public void ApplyTransform(IResamplingImageProcessor processor) + where TPixel : struct, IPixel + => processor.ApplyTransform(in this); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index a6e6bf6126..3e7ccbd0af 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int destinationSize, int sourceSize, MemoryAllocator memoryAllocator) - where TResampler : unmanaged, IResampler + where TResampler : struct, IResampler { double ratio = (double)sourceSize / destinationSize; double scale = ratio; @@ -182,7 +182,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Initializes the kernel map. /// protected internal virtual void Initialize(in TResampler sampler) - where TResampler : unmanaged, IResampler + where TResampler : struct, IResampler { for (int i = 0; i < this.DestinationLength; i++) { @@ -196,7 +196,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// so the data reusable by other data rows. /// private ResizeKernel BuildKernel(in TResampler sampler, int destRowIndex, int dataRowIndex) - where TResampler : unmanaged, IResampler + where TResampler : struct, IResampler { double center = ((destRowIndex + .5) * this.ratio) - .5; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index 520370b6ef..4e6e7a48c1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -17,6 +17,7 @@ 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); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.Transforms.cs similarity index 91% rename from src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.Transforms.cs index 2cd903924a..78f63ee0d3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.Transforms.cs @@ -10,15 +10,15 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Extensions for . + /// Contains the application code for resizing. /// - public static partial class ResamplerExtensions + internal partial class ResizeProcessor + where TPixel : struct, IPixel { /// /// Applies an resizing transformation upon an image. /// /// The type of sampler. - /// The pixel format. /// The configuration. /// The pixel sampler. /// The source image. @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The source bounds. /// The destination location. /// Whether to compress or expand individual pixel color values on processing. - public static void ApplyResizeTransform( + public static void ApplyResizeTransform( Configuration configuration, in TResampler sampler, Image source, @@ -34,8 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle sourceRectangle, Rectangle destinationRectangle, bool compand) - where TResampler : unmanaged, IResampler - where TPixel : struct, IPixel + where TResampler : struct, IResampler { // Handle resize dimensions identical to the original if (source.Width == destination.Width @@ -108,20 +107,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } - private static void ApplyNNResizeFrameTransform( + private static void ApplyNNResizeFrameTransform( Configuration configuration, ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Rectangle destinationRectangle, Rectangle interest) - where TPixel : struct, IPixel { // Scaling factors float widthFactor = sourceRectangle.Width / (float)destinationRectangle.Width; float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height; - var operation = new NNRowIntervalOperation( + var operation = new NNRowIntervalOperation( sourceRectangle, destinationRectangle, widthFactor, @@ -135,7 +133,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms in operation); } - private static void ApplyResizeFrameTransform( + private static void ApplyResizeFrameTransform( Configuration configuration, ImageFrame source, ImageFrame destination, @@ -145,7 +143,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle destinationRectangle, Rectangle interest, bool compand) - where TPixel : struct, IPixel { PixelConversionModifiers conversionModifiers = PixelConversionModifiers.Premultiply.ApplyCompanding(compand); @@ -171,8 +168,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } - private readonly struct NNRowIntervalOperation : IRowIntervalOperation - where TPixel : struct, IPixel + private readonly struct NNRowIntervalOperation : IRowIntervalOperation { private readonly Rectangle sourceBounds; private readonly Rectangle destinationBounds; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 72064d0e36..5b69184d7c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Implements resizing of images using various resamplers. /// /// The pixel format. - internal class ResizeProcessor : TransformProcessor + internal partial class ResizeProcessor : TransformProcessor, IResamplingImageProcessor where TPixel : struct, IPixel { private readonly int destinationWidth; @@ -17,6 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly IResampler resampler; private readonly Rectangle destinationRectangle; private readonly bool compand; + private Image destination; public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) @@ -34,13 +35,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// protected override void BeforeImageApply(Image destination) { - this.resampler.ApplyResizeTransform( - this.Configuration, - this.Source, - destination, - this.SourceRectangle, - this.destinationRectangle, - this.compand); + this.destination = destination; + this.resampler.ApplyTransform(this); base.BeforeImageApply(destination); } @@ -50,5 +46,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { // Everything happens in BeforeImageApply. } + + public void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler => + ApplyResizeTransform( + this.Configuration, + in sampler, + this.Source, + this.destination, + this.SourceRectangle, + this.destinationRectangle, + this.compand); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index 17477c83bd..3d08cf1a4c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; public static ReferenceKernelMap Calculate(in TResampler sampler, int destinationSize, int sourceSize, bool normalize = true) - where TResampler : unmanaged, IResampler + 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 e404c6460a..8dbc056550 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory(Skip = "Only for debugging and development")] [MemberData(nameof(KernelMapData))] public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize) - where TResampler : unmanaged, IResampler + where TResampler : struct, IResampler { var kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false); @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [MemberData(nameof(KernelMapData))] public void KernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) - where TResampler : unmanaged, IResampler + where TResampler : struct, IResampler { this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize); } @@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms #endif private void VerifyKernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) - where TResampler : unmanaged, IResampler + where TResampler : struct, IResampler { var referenceMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize); var kernelMap = ResizeKernelMap.Calculate(in resampler, destSize, srcSize, Configuration.Default.MemoryAllocator);