From 059de8ae0c47dbd1dc0c89d520db87e97868c875 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 21 Feb 2020 13:58:30 +1100 Subject: [PATCH 1/9] Apply Transforms 33% faster --- .../Processing/AffineTransformBuilder.cs | 12 +- .../Transforms/TransformExtensions.cs | 4 +- src/ImageSharp/Processing/KnownResamplers.cs | 34 +-- .../AffineTransformProcessor{TPixel}.cs | 149 +--------- .../Processors/Transforms/IResampler.cs | 37 ++- .../ProjectiveTransformProcessor{TPixel}.cs | 145 +--------- .../ResamplerExtensions.Operations.cs | 263 ++++++++++++++++++ .../Transforms/ResamplerExtensions.cs | 249 +++++++++++++++++ .../Transforms/Resamplers/BicubicResampler.cs | 47 +++- .../Transforms/Resamplers/BoxResampler.cs | 39 ++- .../Resamplers/CatmullRomResampler.cs | 39 ++- .../Transforms/Resamplers/HermiteResampler.cs | 39 ++- .../Resamplers/Lanczos2Resampler.cs | 39 ++- .../Resamplers/Lanczos3Resampler.cs | 39 ++- .../Resamplers/Lanczos5Resampler.cs | 39 ++- .../Resamplers/Lanczos8Resampler.cs | 39 ++- .../Resamplers/MitchellNetravaliResampler.cs | 38 ++- .../Resamplers/NearestNeighborResampler.cs | 44 ++- .../Resamplers/RobidouxResampler.cs | 39 ++- .../Resamplers/RobidouxSharpResampler.cs | 39 ++- .../Transforms/Resamplers/SplineResampler.cs | 39 ++- .../Resamplers/TriangleResampler.cs | 39 ++- .../Transforms/Resamplers/WelchResampler.cs | 39 ++- .../Processors/Transforms/RotateProcessor.cs | 4 +- .../Processors/Transforms/SkewProcessor.cs | 4 +- .../Transforms/TransformKernelMap.cs | 160 ----------- ...ransformUtils.cs => TransformUtilities.cs} | 13 +- .../Processing/ProjectiveTransformBuilder.cs | 12 +- .../Transforms/TransformBuilderTestBase.cs | 4 +- 29 files changed, 1141 insertions(+), 546 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs create mode 100644 src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs rename src/ImageSharp/Processing/Processors/Transforms/{TransformUtils.cs => TransformUtilities.cs} (96%) diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index 90e00924a..dde7beb3e 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public AffineTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + => this.Prepend(size => TransformUtilities.CreateRotationMatrixRadians(radians, size)); /// /// Prepends a rotation matrix using the given rotation in degrees at the given origin. @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public AffineTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + => this.Append(size => TransformUtilities.CreateRotationMatrixRadians(radians, size)); /// /// Appends a rotation matrix using the given rotation in degrees at the given origin. @@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in degrees. /// The . public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) - => this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + => this.Prepend(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size)); /// /// Prepends a centered skew matrix from the give angles in radians. @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + => this.Prepend(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size)); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in degrees. /// The . public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) - => this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + => this.Append(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size)); /// /// Appends a centered skew matrix from the give angles in radians. @@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + => this.Append(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size)); /// /// Appends a skew matrix using the given angles in degrees at the given origin. diff --git a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs index 630564955..ee8f3854e 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing IResampler sampler) { Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); + Size targetDimensions = TransformUtilities.GetTransformedSize(sourceRectangle.Size, transform); return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); } @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing IResampler sampler) { Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); + Size targetDimensions = TransformUtilities.GetTransformedSize(sourceRectangle.Size, transform); return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); } diff --git a/src/ImageSharp/Processing/KnownResamplers.cs b/src/ImageSharp/Processing/KnownResamplers.cs index 621215b28..6c73513c8 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 0d9055f34..130cc1bf0 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 6db03d5b4..fb095b70a 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 da071e3f2..50315ac95 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 000000000..96fcc49f7 --- /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 000000000..674b7f423 --- /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 199563bc7..ea6871575 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 0667226d9..49b53378f 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 8995d2d8a..5a2992595 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 18c3fda7c..80aa69acd 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 2294696de..3228a709d 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 95fb206a9..a9388575b 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 c99ed1e85..7662f2616 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 4efdb882b..e886f41db 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 d4ba954f2..ef97be92b 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 1f12334f4..e4cec5f4d 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 51938566c..6d9e7641e 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 015b7f0af..eaf5fa6e8 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 df6c2a338..d2608b2fa 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 57d1fa11d..b40362124 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 edce5fcf9..8a92ea7c0 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 aae66e9ea..b53e7b5c0 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 4d0733334..1bcfa5fd2 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 a0d44cb7a..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Contains the methods required to calculate transform kernel convolution. - /// - internal class TransformKernelMap : IDisposable - { - private readonly Buffer2D yBuffer; - private readonly Buffer2D xBuffer; - private readonly Vector2 extents; - private Vector4 maxSourceExtents; - private readonly IResampler sampler; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The source size. - /// The destination size. - /// The sampler. - public TransformKernelMap(Configuration configuration, Size source, Size destination, IResampler sampler) - { - this.sampler = sampler; - float yRadius = this.GetSamplingRadius(source.Height, destination.Height); - float xRadius = this.GetSamplingRadius(source.Width, destination.Width); - - this.extents = new Vector2(xRadius, yRadius); - int xLength = (int)MathF.Ceiling((this.extents.X * 2) + 2); - int yLength = (int)MathF.Ceiling((this.extents.Y * 2) + 2); - - // We use 2D buffers so that we can access the weight spans per row in parallel. - this.yBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height); - this.xBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height); - - int maxX = source.Width - 1; - int maxY = source.Height - 1; - this.maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); - } - - /// - /// Gets a reference to the first item of the y window. - /// - /// The reference to the first item of the window. - [MethodImpl(InliningOptions.ShortMethod)] - public ref float GetYStartReference(int y) - => ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpan(y)); - - /// - /// Gets a reference to the first item of the x window. - /// - /// The reference to the first item of the window. - [MethodImpl(InliningOptions.ShortMethod)] - public ref float GetXStartReference(int y) - => ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpan(y)); - - public void Convolve( - Vector2 transformedPoint, - int column, - ref float ySpanRef, - ref float xSpanRef, - Buffer2D sourcePixels, - Span targetRow) - where TPixel : struct, IPixel - { - // Clamp sampling pixel radial extents to the source image edges - Vector2 minXY = transformedPoint - this.extents; - Vector2 maxXY = transformedPoint + this.extents; - - // left, top, right, bottom - var extents = new Vector4( - MathF.Ceiling(minXY.X - .5F), - MathF.Ceiling(minXY.Y - .5F), - MathF.Floor(maxXY.X + .5F), - MathF.Floor(maxXY.Y + .5F)); - - extents = Vector4.Clamp(extents, Vector4.Zero, this.maxSourceExtents); - - int left = (int)extents.X; - int top = (int)extents.Y; - int right = (int)extents.Z; - int bottom = (int)extents.W; - - if (left == right || top == bottom) - { - return; - } - - this.CalculateWeights(top, bottom, transformedPoint.Y, ref ySpanRef); - this.CalculateWeights(left, right, transformedPoint.X, ref xSpanRef); - - Vector4 sum = Vector4.Zero; - for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++) - { - float yWeight = Unsafe.Add(ref ySpanRef, kernelY); - - for (int kernelX = 0, x = left; x <= right; x++, kernelX++) - { - float xWeight = Unsafe.Add(ref xSpanRef, kernelX); - - // Values are first premultiplied to prevent darkening of edge pixels. - var current = sourcePixels[x, y].ToVector4(); - Vector4Utils.Premultiply(ref current); - sum += current * xWeight * yWeight; - } - } - - // Reverse the premultiplication - Vector4Utils.UnPremultiply(ref sum); - targetRow[column] = sum; - } - - /// - /// Calculated the normalized weights for the given point. - /// - /// The minimum sampling offset - /// The maximum sampling offset - /// The transformed point dimension - /// The reference to the collection of weights - [MethodImpl(InliningOptions.ShortMethod)] - private void CalculateWeights(int min, int max, float point, ref float weightsRef) - { - float sum = 0; - for (int x = 0, i = min; i <= max; i++, x++) - { - float weight = this.sampler.GetValue(i - point); - sum += weight; - Unsafe.Add(ref weightsRef, x) = weight; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private float GetSamplingRadius(int sourceSize, int destinationSize) - { - float scale = (float)sourceSize / destinationSize; - - if (scale < 1F) - { - scale = 1F; - } - - return MathF.Ceiling(scale * this.sampler.Radius); - } - - public void Dispose() - { - this.yBuffer?.Dispose(); - this.xBuffer?.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs similarity index 96% rename from src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs rename to src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs index e0fb55438..0760d2e3e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Contains utility methods for working with transforms. /// - internal static class TransformUtils + internal static class TransformUtilities { /// /// Applies the projective transform against the given coordinates flattened into the 2D space. @@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The amount of rotation, in degrees. /// The source image size. /// The . + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix3x2 CreateRotationMatrixDegrees(float degrees, Size size) => CreateCenteredTransformMatrix( new Rectangle(Point.Empty, size), @@ -44,6 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The amount of rotation, in radians. /// The source image size. /// The . + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size) => CreateCenteredTransformMatrix( new Rectangle(Point.Empty, size), @@ -56,6 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The Y angle, in degrees. /// The source image size. /// The . + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size) => CreateCenteredTransformMatrix( new Rectangle(Point.Empty, size), @@ -68,6 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The Y angle, in radians. /// The source image size. /// The . + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix3x2 CreateSkewMatrixRadians(float radiansX, float radiansY, Size size) => CreateCenteredTransformMatrix( new Rectangle(Point.Empty, size), @@ -79,6 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The source image bounds. /// The transformation matrix. /// The + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) { Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); @@ -105,6 +110,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// An enumeration that indicates on which corners to taper the rectangle. /// The amount to taper. /// The + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction) { Matrix4x4 matrix = Matrix4x4.Identity; @@ -225,6 +231,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The . /// + [MethodImpl(InliningOptions.ShortMethod)] public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) { Rectangle transformed = GetTransformedRectangle(rectangle, matrix); @@ -284,6 +291,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The . /// + [MethodImpl(InliningOptions.ShortMethod)] public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) { if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) @@ -307,6 +315,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The . /// + [MethodImpl(InliningOptions.ShortMethod)] public static Size GetTransformedSize(Size size, Matrix4x4 matrix) { Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); @@ -321,6 +330,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ConstrainSize(rectangle); } + [MethodImpl(InliningOptions.ShortMethod)] private static Size ConstrainSize(Rectangle rectangle) { // We want to resize the canvas here taking into account any translations. @@ -342,6 +352,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return new Size(width, height); } + [MethodImpl(InliningOptions.ShortMethod)] private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) { // Find the minimum and maximum "corners" based on the given vectors diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index 0ff693d81..ef44dc16d 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount to taper. /// The . public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Prepend(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction)); /// /// Appends a matrix that performs a tapering projective transform. @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount to taper. /// The . public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Append(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction)); /// /// Prepends a centered rotation matrix using the given rotation in degrees. @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); + => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size))); /// /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); + => this.Append(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size))); /// /// Appends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size))); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -205,7 +205,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + => this.Append(size => new Matrix4x4(TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size))); /// /// Appends a skew matrix using the given angles in degrees at the given origin. diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index e1b6e18a7..21359799e 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.AppendRotationDegrees(builder, degrees); // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness - Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, size); + Matrix3x2 matrix = TransformUtilities.CreateRotationMatrixDegrees(degrees, size); var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.AppendSkewDegrees(builder, degreesX, degreesY); - Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size); + Matrix3x2 matrix = TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size); var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); From d98f65a749c21fa741c465bd15fb5073807145d4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 21 Feb 2020 15:09:38 +1100 Subject: [PATCH 2/9] Update ResamplerExtensions.cs --- .../Processing/Processors/Transforms/ResamplerExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs index 674b7f423..245adb238 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms 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()); + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); return; } @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms 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()); + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); return; } From 52e02a32984109162597d2891a069f3cdae6f14d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 21 Feb 2020 22:44:55 +1100 Subject: [PATCH 3/9] Inline resizing sampler. --- .../CloningImageProcessor{TPixel}.cs | 18 +- .../AffineTransformProcessor{TPixel}.cs | 2 +- .../Transforms/CropProcessor{TPixel}.cs | 2 +- .../Processors/Transforms/IResampler.cs | 19 ++ .../ProjectiveTransformProcessor{TPixel}.cs | 2 +- .../Transforms/Resamplers/BicubicResampler.cs | 18 ++ .../Transforms/Resamplers/BoxResampler.cs | 18 ++ .../Resamplers/CatmullRomResampler.cs | 18 ++ .../Transforms/Resamplers/HermiteResampler.cs | 18 ++ .../Resamplers/Lanczos2Resampler.cs | 18 ++ .../Resamplers/Lanczos3Resampler.cs | 18 ++ .../Resamplers/Lanczos5Resampler.cs | 18 ++ .../Resamplers/Lanczos8Resampler.cs | 18 ++ .../Resamplers/MitchellNetravaliResampler.cs | 18 ++ .../Resamplers/NearestNeighborResampler.cs | 18 ++ .../Resamplers/RobidouxResampler.cs | 18 ++ .../Resamplers/RobidouxSharpResampler.cs | 18 ++ .../Transforms/Resamplers/SplineResampler.cs | 18 ++ .../Resamplers/TriangleResampler.cs | 18 ++ .../Transforms/Resamplers/WelchResampler.cs | 18 ++ .../Transforms/Resize/ResamplerExtensions.cs | 227 ++++++++++++++++++ .../Transforms/Resize/ResizeKernel.cs | 21 +- .../ResizeKernelMap.PeriodicKernelMap.cs | 24 +- .../Transforms/Resize/ResizeKernelMap.cs | 55 +++-- .../Transforms/Resize/ResizeProcessor.cs | 16 +- .../Resize/ResizeProcessor{TPixel}.cs | 178 ++------------ .../Transforms/Resize/ResizeWorker.cs | 13 +- ...ResizeKernelMapTests.ReferenceKernelMap.cs | 7 +- .../Transforms/ResizeKernelMapTests.cs | 129 +++++----- .../Processors/Transforms/ResizeTests.cs | 4 +- .../Processing/Transforms/PadTest.cs | 8 +- .../Processing/Transforms/ResizeTests.cs | 16 +- 32 files changed, 704 insertions(+), 307 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs index c539861f9..1b6438bf3 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs @@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.Processing.Processors } /// - /// Gets the size of the target image. + /// Gets the size of the destination image. /// /// The . - protected abstract Size GetTargetSize(); + protected abstract Size GetDestinationSize(); /// /// This method is called before the process is applied to prepare the processor. @@ -168,21 +168,21 @@ namespace SixLabors.ImageSharp.Processing.Processors private Image CreateTarget() { Image source = this.Source; - Size targetSize = this.GetTargetSize(); + Size destinationSize = this.GetDestinationSize(); // We will always be creating the clone even for mutate because we may need to resize the canvas. - var targetFrames = new ImageFrame[source.Frames.Count]; - for (int i = 0; i < targetFrames.Length; i++) + var destinationFrames = new ImageFrame[source.Frames.Count]; + for (int i = 0; i < destinationFrames.Length; i++) { - targetFrames[i] = new ImageFrame( + destinationFrames[i] = new ImageFrame( this.Configuration, - targetSize.Width, - targetSize.Height, + destinationSize.Width, + destinationSize.Height, source.Frames[i].Metadata.DeepClone()); } // Use the overload to prevent an extra frame being added. - return new Image(this.Configuration, source.Metadata.DeepClone(), targetFrames); + return new Image(this.Configuration, source.Metadata.DeepClone(), destinationFrames); } private void CheckFrameCount(Image a, Image b) diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 130cc1bf0..9d01c049c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.resampler = definition.Sampler; } - protected override Size GetTargetSize() => this.targetSize; + protected override Size GetDestinationSize() => this.targetSize; /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index e8eeea3cb..a80eef2bc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms => this.cropRectangle = definition.CropRectangle; /// - protected override Size GetTargetSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height); + protected override Size GetDestinationSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height); /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs index fb095b70a..c7557461a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs @@ -25,6 +25,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// float GetValue(float x); + /// + /// Applies an resizing 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. /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index 50315ac95..afc4658b4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.resampler = definition.Sampler; } - protected override Size GetTargetSize() => this.targetSize; + protected override Size GetDestinationSize() => this.targetSize; /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs index ea6871575..5f9669f6f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs @@ -41,6 +41,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0; } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs index 49b53378f..ecaa28c80 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs @@ -28,6 +28,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0; } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs index 5a2992595..f62b7d989 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs @@ -28,6 +28,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs index 80aa69acd..883d4b684 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs @@ -27,6 +27,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs index 3228a709d..93174a23a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs @@ -34,6 +34,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs index a9388575b..6c008be63 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs @@ -34,6 +34,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs index 7662f2616..56da01fb2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs @@ -34,6 +34,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs index e886f41db..d24c7a1f0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs @@ -34,6 +34,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs index ef97be92b..e67e50ab0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs @@ -25,6 +25,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs index e4cec5f4d..94b0b0405 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs @@ -20,6 +20,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) => x; + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs index 6d9e7641e..1b4d84e5a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs @@ -26,6 +26,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs index eaf5fa6e8..52991a649 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs @@ -26,6 +26,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs index d2608b2fa..a21ed495b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs @@ -26,6 +26,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ImageMaths.GetBcValue(x, B, C); } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs index b40362124..c8409e185 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs @@ -34,6 +34,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs index 8a92ea7c0..673cbd5d7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs @@ -33,6 +33,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + /// + [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( diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs new file mode 100644 index 000000000..b681a436c --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs @@ -0,0 +1,227 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +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 resizing transformation upon an image. + /// + /// The type of sampler. + /// The pixel format. + /// The configuration. + /// The pixel sampler. + /// The source image. + /// The destination image. + /// The source bounds. + /// The destination location. + /// Whether to compress or expand individual pixel color values on processing. + public static void ApplyResizeTransform( + Configuration configuration, + in TResampler sampler, + Image source, + Image destination, + Rectangle sourceRectangle, + Rectangle destinationRectangle, + bool compand) + where TResampler : unmanaged, IResampler + where TPixel : struct, IPixel + { + // Handle resize dimensions identical to the original + if (source.Width == destination.Width + && source.Height == destination.Height + && sourceRectangle == destinationRectangle) + { + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; + + // The cloned will be blank here copy all the pixel data over + sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup()); + } + + return; + } + + var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds()); + + if (sampler is NearestNeighborResampler) + { + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; + + ApplyNNResizeFrameTransform( + configuration, + sourceFrame, + destinationFrame, + sourceRectangle, + destinationRectangle, + interest); + } + + return; + } + + // Since all image frame dimensions have to be the same we can calculate + // the kernel maps and reuse for all frames. + MemoryAllocator allocator = configuration.MemoryAllocator; + using var horizontalKernelMap = ResizeKernelMap.Calculate( + in sampler, + destinationRectangle.Width, + sourceRectangle.Width, + allocator); + + using var verticalKernelMap = ResizeKernelMap.Calculate( + in sampler, + destinationRectangle.Height, + sourceRectangle.Height, + allocator); + + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; + + ApplyResizeFrameTransform( + configuration, + sourceFrame, + destinationFrame, + horizontalKernelMap, + verticalKernelMap, + sourceRectangle, + destinationRectangle, + interest, + compand); + } + } + + 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( + sourceRectangle, + destinationRectangle, + widthFactor, + heightFactor, + source, + destination); + + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } + + private static void ApplyResizeFrameTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, + Rectangle sourceRectangle, + Rectangle destinationRectangle, + Rectangle interest, + bool compand) + where TResampler : unmanaged, IResampler + where TPixel : struct, IPixel + { + PixelConversionModifiers conversionModifiers = + PixelConversionModifiers.Premultiply.ApplyCompanding(compand); + + BufferArea sourceArea = source.PixelBuffer.GetArea(sourceRectangle); + + // To reintroduce parallel processing, we would launch multiple workers + // for different row intervals of the image. + using (var worker = new ResizeWorker( + configuration, + sourceArea, + conversionModifiers, + horizontalKernelMap, + verticalKernelMap, + destination.Width, + interest, + destinationRectangle.Location)) + { + worker.Initialize(); + + var workingInterval = new RowInterval(interest.Top, interest.Bottom); + worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); + } + } + + private readonly struct NNRowIntervalOperation : IRowIntervalOperation + where TPixel : struct, IPixel + { + private readonly Rectangle sourceBounds; + private readonly Rectangle destinationBounds; + private readonly float widthFactor; + private readonly float heightFactor; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNRowIntervalOperation( + Rectangle sourceBounds, + Rectangle destinationBounds, + float widthFactor, + float heightFactor, + ImageFrame source, + ImageFrame destination) + { + this.sourceBounds = sourceBounds; + this.destinationBounds = destinationBounds; + this.widthFactor = widthFactor; + this.heightFactor = heightFactor; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int sourceX = this.sourceBounds.X; + int sourceY = this.sourceBounds.Y; + int destX = this.destinationBounds.X; + int destY = this.destinationBounds.Y; + int destLeft = this.destinationBounds.Left; + int destRight = this.destinationBounds.Right; + + for (int y = rows.Min; y < rows.Max; y++) + { + // Y coordinates of source points + Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.GetPixelRowSpan(y); + + for (int x = destLeft; x < destRight; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; + } + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 14bf552b9..83bee9111 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Points to a collection of of weights allocated in . + /// Points to a collection of of weights allocated in . /// internal readonly unsafe struct ResizeKernel { @@ -28,15 +28,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Gets the start index for the destination row. /// - public int StartIndex { get; } + public int StartIndex + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// /// Gets the the length of the kernel. /// - public int Length { get; } + public int Length + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// - /// Gets the span representing the portion of the that this window covers. + /// Gets the span representing the portion of the that this window covers. /// /// The . /// @@ -81,6 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Copy the contents of altering /// to the value . /// + [MethodImpl(InliningOptions.ShortMethod)] internal ResizeKernel AlterLeftValue(int left) { return new ResizeKernel(left, this.bufferPtr, this.Length); @@ -96,4 +105,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs index be2546369..52a308cf2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs @@ -1,19 +1,17 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - /// Contains - /// - internal partial class ResizeKernelMap + internal partial class ResizeKernelMap + where TResampler : unmanaged, IResampler { /// - /// Memory-optimized where repeating rows are stored only once. + /// Memory-optimized where repeating rows are stored only once. /// - private sealed class PeriodicKernelMap : ResizeKernelMap + private sealed class PeriodicKernelMap : ResizeKernelMap { private readonly int period; @@ -21,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public PeriodicKernelMap( MemoryAllocator memoryAllocator, - IResampler sampler, + TResampler sampler, int sourceLength, int destinationLength, double ratio, @@ -45,15 +43,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; - protected override void Initialize() + protected internal override void Initialize() { // Build top corner data + one period of the mosaic data: int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; for (int i = 0; i < startOfFirstRepeatedMosaic; i++) { - ResizeKernel kernel = this.BuildKernel(i, i); - this.kernels[i] = kernel; + this.kernels[i] = this.BuildKernel(i, i); } // Copy the mosaics: @@ -70,10 +67,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int bottomStartData = this.cornerInterval + this.period; for (int i = 0; i < this.cornerInterval; i++) { - ResizeKernel kernel = this.BuildKernel(bottomStartDest + i, bottomStartData + i); - this.kernels[bottomStartDest + i] = kernel; + this.kernels[bottomStartDest + i] = this.BuildKernel(bottomStartDest + i, bottomStartData + i); } } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index cc9516956..8432eb654 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -5,20 +5,20 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides values from an optimized, - /// contiguous memory region. + /// Provides resize kernel values from an optimized contiguous memory region. /// - internal partial class ResizeKernelMap : IDisposable + /// The type of sampler. + internal partial class ResizeKernelMap : IDisposable + where TResampler : unmanaged, IResampler { private static readonly TolerantMath TolerantMath = TolerantMath.Default; - private readonly IResampler sampler; + private readonly TResampler sampler; private readonly int sourceLength; @@ -34,12 +34,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ResizeKernel[] kernels; + private bool isDisposed; + // To avoid both GC allocations, and MemoryAllocator ceremony: private readonly double[] tempValues; private ResizeKernelMap( MemoryAllocator memoryAllocator, - IResampler sampler, + TResampler sampler, int sourceLength, int destinationLength, int bufferHeight, @@ -77,19 +79,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms $"radius:{this.radius}|sourceSize:{this.sourceLength}|destinationSize:{this.DestinationLength}|ratio:{this.ratio}|scale:{this.scale}"; /// - /// Disposes instance releasing it's backing buffer. + /// Disposes instance releasing it's backing buffer. /// public void Dispose() + => this.Dispose(true); + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose of managed and unmanaged objects. + protected virtual void Dispose(bool disposing) { - this.pinHandle.Dispose(); - this.data.Dispose(); + if (!this.isDisposed) + { + this.isDisposed = true; + + if (disposing) + { + this.pinHandle.Dispose(); + this.data.Dispose(); + } + } } /// /// Returns a for an index value between 0 and DestinationSize - 1. /// [MethodImpl(InliningOptions.ShortMethod)] - public ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; + internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; /// /// Computes the weights to apply at each pixel when resizing. @@ -98,9 +115,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The destination size /// The source size /// The to use for buffer allocations - /// The - public static ResizeKernelMap Calculate( - IResampler sampler, + /// The + public static ResizeKernelMap Calculate( + in TResampler sampler, int destinationSize, int sourceSize, MemoryAllocator memoryAllocator) @@ -141,7 +158,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // If we don't have at least 2 periods, we go with the basic implementation: bool hasAtLeast2Periods = 2 * (cornerInterval + period) < destinationSize; - ResizeKernelMap result = hasAtLeast2Periods + ResizeKernelMap result = hasAtLeast2Periods ? new PeriodicKernelMap( memoryAllocator, sampler, @@ -152,7 +169,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms radius, period, cornerInterval) - : new ResizeKernelMap( + : new ResizeKernelMap( memoryAllocator, sampler, sourceSize, @@ -167,12 +184,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return result; } - protected virtual void Initialize() + /// + /// Initializes the kernel map. + /// + protected internal virtual void Initialize() { for (int i = 0; i < this.DestinationLength; i++) { - ResizeKernel kernel = this.BuildKernel(i, i); - this.kernels[i] = kernel; + this.kernels[i] = this.BuildKernel(i, i); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index ec1f94c14..520370b6e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -21,9 +21,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options); this.Sampler = options.Sampler; - this.TargetWidth = size.Width; - this.TargetHeight = size.Height; - this.TargetRectangle = rectangle; + this.DestinationWidth = size.Width; + this.DestinationHeight = size.Height; + this.DestinationRectangle = rectangle; this.Compand = options.Compand; } @@ -33,19 +33,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public IResampler Sampler { get; } /// - /// Gets the target width. + /// Gets the destination width. /// - public int TargetWidth { get; } + public int DestinationWidth { get; } /// - /// Gets the target height. + /// Gets the destination height. /// - public int TargetHeight { get; } + public int DestinationHeight { get; } /// /// Gets the resize rectangle. /// - public Rectangle TargetRectangle { get; } + public Rectangle DestinationRectangle { get; } /// /// Gets a value indicating whether to compress or expand individual pixel color values on processing. diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 92c1b71fe..72064d0e3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -1,10 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -12,59 +8,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Implements resizing of images using various resamplers. /// - /// - /// The original code has been adapted from . - /// /// The pixel format. internal class ResizeProcessor : TransformProcessor where TPixel : struct, IPixel { - private bool isDisposed; - private readonly int targetWidth; - private readonly int targetHeight; + private readonly int destinationWidth; + private readonly int destinationHeight; private readonly IResampler resampler; - private readonly Rectangle targetRectangle; + private readonly Rectangle destinationRectangle; private readonly bool compand; - // The following fields are not immutable but are optionally created on demand. - private ResizeKernelMap horizontalKernelMap; - private ResizeKernelMap verticalKernelMap; - public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.targetWidth = definition.TargetWidth; - this.targetHeight = definition.TargetHeight; - this.targetRectangle = definition.TargetRectangle; + this.destinationWidth = definition.DestinationWidth; + this.destinationHeight = definition.DestinationHeight; + this.destinationRectangle = definition.DestinationRectangle; this.resampler = definition.Sampler; this.compand = definition.Compand; } /// - protected override Size GetTargetSize() => new Size(this.targetWidth, this.targetHeight); + protected override Size GetDestinationSize() => new Size(this.destinationWidth, this.destinationHeight); /// protected override void BeforeImageApply(Image destination) { - if (!(this.resampler is NearestNeighborResampler)) - { - Image source = this.Source; - Rectangle sourceRectangle = this.SourceRectangle; - - // Since all image frame dimensions have to be the same we can calculate this for all frames. - MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); - this.horizontalKernelMap = ResizeKernelMap.Calculate( - this.resampler, - this.targetRectangle.Width, - sourceRectangle.Width, - memoryAllocator); - - this.verticalKernelMap = ResizeKernelMap.Calculate( - this.resampler, - this.targetRectangle.Height, - sourceRectangle.Height, - memoryAllocator); - } + this.resampler.ApplyResizeTransform( + this.Configuration, + this.Source, + destination, + this.SourceRectangle, + this.destinationRectangle, + this.compand); base.BeforeImageApply(destination); } @@ -72,131 +48,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) { - Rectangle sourceRectangle = this.SourceRectangle; - Configuration configuration = this.Configuration; - - // Handle resize dimensions identical to the original - if (source.Width == destination.Width - && source.Height == destination.Height - && sourceRectangle == this.targetRectangle) - { - // The cloned will be blank here copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); - return; - } - - int width = this.targetWidth; - int height = this.targetHeight; - var interest = Rectangle.Intersect(this.targetRectangle, new Rectangle(0, 0, width, height)); - - if (this.resampler is NearestNeighborResampler) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; - - var operation = new RowIntervalOperation(sourceRectangle, this.targetRectangle, widthFactor, heightFactor, source, destination); - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); - - return; - } - - PixelConversionModifiers conversionModifiers = - PixelConversionModifiers.Premultiply.ApplyCompanding(this.compand); - - BufferArea sourceArea = source.PixelBuffer.GetArea(sourceRectangle); - - // To reintroduce parallel processing, we to launch multiple workers - // for different row intervals of the image. - using (var worker = new ResizeWorker( - configuration, - sourceArea, - conversionModifiers, - this.horizontalKernelMap, - this.verticalKernelMap, - width, - interest, - this.targetRectangle.Location)) - { - worker.Initialize(); - - var workingInterval = new RowInterval(interest.Top, interest.Bottom); - worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); - } - } - - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - this.horizontalKernelMap?.Dispose(); - this.horizontalKernelMap = null; - this.verticalKernelMap?.Dispose(); - this.verticalKernelMap = null; - } - - this.isDisposed = true; - base.Dispose(disposing); - } - - private readonly struct RowIntervalOperation : IRowIntervalOperation - { - private readonly Rectangle sourceBounds; - private readonly Rectangle destinationBounds; - private readonly float widthFactor; - private readonly float heightFactor; - private readonly ImageFrame source; - private readonly ImageFrame destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public RowIntervalOperation( - Rectangle sourceBounds, - Rectangle destinationBounds, - float widthFactor, - float heightFactor, - ImageFrame source, - ImageFrame destination) - { - this.sourceBounds = sourceBounds; - this.destinationBounds = destinationBounds; - this.widthFactor = widthFactor; - this.heightFactor = heightFactor; - this.source = source; - this.destination = destination; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - int sourceX = this.sourceBounds.X; - int sourceY = this.sourceBounds.Y; - int destX = this.destinationBounds.X; - int destY = this.destinationBounds.Y; - int destLeft = this.destinationBounds.Left; - int destRight = this.destinationBounds.Right; - - for (int y = rows.Min; y < rows.Max; y++) - { - // Y coordinates of source points - Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); - Span targetRow = this.destination.GetPixelRowSpan(y); - - for (int x = destLeft; x < destRight; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; - } - } - } + // Everything happens in BeforeImageApply. } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index de339823e..cbec5242c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -19,7 +19,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// When sliding the window, the contents of the bottom window band are copied to the new top band. /// For more details, and visual explanation, see "ResizeWorker.pptx". /// - internal sealed class ResizeWorker : IDisposable + internal sealed class ResizeWorker : IDisposable + where TResampler : unmanaged, IResampler where TPixel : struct, IPixel { private readonly Buffer2D transposedFirstPassBuffer; @@ -28,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly PixelConversionModifiers conversionModifiers; - private readonly ResizeKernelMap horizontalKernelMap; + private readonly ResizeKernelMap horizontalKernelMap; private readonly BufferArea source; @@ -38,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly IMemoryOwner tempColumnBuffer; - private readonly ResizeKernelMap verticalKernelMap; + private readonly ResizeKernelMap verticalKernelMap; private readonly int destWidth; @@ -56,8 +57,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Configuration configuration, BufferArea source, PixelConversionModifiers conversionModifiers, - ResizeKernelMap horizontalKernelMap, - ResizeKernelMap verticalKernelMap, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, int destWidth, Rectangle targetWorkingRect, Point targetOrigin) @@ -104,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.tempColumnBuffer.Dispose(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Span GetColumnSpan(int x, int startY) { return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index beb7ebc9c..d6fa07536 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -13,7 +13,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms /// /// Simplified reference implementation for functionality. /// - internal class ReferenceKernelMap + internal class ReferenceKernelMap + where TResampler : unmanaged, IResampler { private readonly ReferenceKernel[] kernels; @@ -26,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; - public static ReferenceKernelMap Calculate(IResampler sampler, int destinationSize, int sourceSize, bool normalize = true) + public static ReferenceKernelMap Calculate(TResampler sampler, int destinationSize, int sourceSize, bool normalize = true) { double ratio = (double)sourceSize / destinationSize; double scale = ratio; @@ -84,7 +85,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms result.Add(new ReferenceKernel(left, floatVals)); } - return new ReferenceKernelMap(result.ToArray()); + return new ReferenceKernelMap(result.ToArray()); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs index 08745d570..6ca3c3bee 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -25,59 +25,60 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms /// /// resamplerName, srcSize, destSize /// - public static readonly TheoryData KernelMapData = new TheoryData + public static readonly TheoryData KernelMapData + = new TheoryData { - { nameof(KnownResamplers.Bicubic), 15, 10 }, - { nameof(KnownResamplers.Bicubic), 10, 15 }, - { nameof(KnownResamplers.Bicubic), 20, 20 }, - { nameof(KnownResamplers.Bicubic), 50, 40 }, - { nameof(KnownResamplers.Bicubic), 40, 50 }, - { nameof(KnownResamplers.Bicubic), 500, 200 }, - { nameof(KnownResamplers.Bicubic), 200, 500 }, - { nameof(KnownResamplers.Bicubic), 3032, 400 }, - { nameof(KnownResamplers.Bicubic), 10, 25 }, - { nameof(KnownResamplers.Lanczos3), 16, 12 }, - { nameof(KnownResamplers.Lanczos3), 12, 16 }, - { nameof(KnownResamplers.Lanczos3), 12, 9 }, - { nameof(KnownResamplers.Lanczos3), 9, 12 }, - { nameof(KnownResamplers.Lanczos3), 6, 8 }, - { nameof(KnownResamplers.Lanczos3), 8, 6 }, - { nameof(KnownResamplers.Lanczos3), 20, 12 }, - { nameof(KnownResamplers.Lanczos3), 5, 25 }, - { nameof(KnownResamplers.Lanczos3), 5, 50 }, - { nameof(KnownResamplers.Lanczos3), 25, 5 }, - { nameof(KnownResamplers.Lanczos3), 50, 5 }, - { nameof(KnownResamplers.Lanczos3), 49, 5 }, - { nameof(KnownResamplers.Lanczos3), 31, 5 }, - { nameof(KnownResamplers.Lanczos8), 500, 200 }, - { nameof(KnownResamplers.Lanczos8), 100, 10 }, - { nameof(KnownResamplers.Lanczos8), 100, 80 }, - { nameof(KnownResamplers.Lanczos8), 10, 100 }, + { KnownResamplers.Bicubic, 15, 10 }, + { KnownResamplers.Bicubic, 10, 15 }, + { KnownResamplers.Bicubic, 20, 20 }, + { KnownResamplers.Bicubic, 50, 40 }, + { KnownResamplers.Bicubic, 40, 50 }, + { KnownResamplers.Bicubic, 500, 200 }, + { KnownResamplers.Bicubic, 200, 500 }, + { KnownResamplers.Bicubic, 3032, 400 }, + { KnownResamplers.Bicubic, 10, 25 }, + { KnownResamplers.Lanczos3, 16, 12 }, + { KnownResamplers.Lanczos3, 12, 16 }, + { KnownResamplers.Lanczos3, 12, 9 }, + { KnownResamplers.Lanczos3, 9, 12 }, + { KnownResamplers.Lanczos3, 6, 8 }, + { KnownResamplers.Lanczos3, 8, 6 }, + { KnownResamplers.Lanczos3, 20, 12 }, + { KnownResamplers.Lanczos3, 5, 25 }, + { KnownResamplers.Lanczos3, 5, 50 }, + { KnownResamplers.Lanczos3, 25, 5 }, + { KnownResamplers.Lanczos3, 50, 5 }, + { KnownResamplers.Lanczos3, 49, 5 }, + { KnownResamplers.Lanczos3, 31, 5 }, + { KnownResamplers.Lanczos8, 500, 200 }, + { KnownResamplers.Lanczos8, 100, 10 }, + { KnownResamplers.Lanczos8, 100, 80 }, + { KnownResamplers.Lanczos8, 10, 100 }, // Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5: - { nameof(KnownResamplers.Box), 378, 149 }, - { nameof(KnownResamplers.Box), 349, 174 }, + { KnownResamplers.Box, 378, 149 }, + { KnownResamplers.Box, 349, 174 }, // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData - { nameof(KnownResamplers.Box), 201, 100 }, - { nameof(KnownResamplers.Box), 199, 99 }, - { nameof(KnownResamplers.Box), 10, 299 }, - { nameof(KnownResamplers.Box), 299, 10 }, - { nameof(KnownResamplers.Box), 301, 300 }, - { nameof(KnownResamplers.Box), 1180, 480 }, - { nameof(KnownResamplers.Lanczos2), 3264, 3032 }, - { nameof(KnownResamplers.Bicubic), 1280, 2240 }, - { nameof(KnownResamplers.Bicubic), 1920, 1680 }, - { nameof(KnownResamplers.Bicubic), 3072, 2240 }, - { nameof(KnownResamplers.Welch), 300, 2008 }, + { KnownResamplers.Box, 201, 100 }, + { KnownResamplers.Box, 199, 99 }, + { KnownResamplers.Box, 10, 299 }, + { KnownResamplers.Box, 299, 10 }, + { KnownResamplers.Box, 301, 300 }, + { KnownResamplers.Box, 1180, 480 }, + { KnownResamplers.Lanczos2, 3264, 3032 }, + { KnownResamplers.Bicubic, 1280, 2240 }, + { KnownResamplers.Bicubic, 1920, 1680 }, + { KnownResamplers.Bicubic, 3072, 2240 }, + { KnownResamplers.Welch, 300, 2008 }, // ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData - { nameof(KnownResamplers.Bicubic), 10, 50 }, - { nameof(KnownResamplers.Bicubic), 49, 301 }, - { nameof(KnownResamplers.Bicubic), 301, 49 }, - { nameof(KnownResamplers.Bicubic), 1680, 1200 }, - { nameof(KnownResamplers.Box), 13, 299 }, - { nameof(KnownResamplers.Lanczos5), 3032, 600 }, + { KnownResamplers.Bicubic, 10, 50 }, + { KnownResamplers.Bicubic, 49, 301 }, + { KnownResamplers.Bicubic, 301, 49 }, + { KnownResamplers.Bicubic, 1680, 1200 }, + { KnownResamplers.Box, 13, 299 }, + { KnownResamplers.Lanczos5, 3032, 600 }, }; public static TheoryData GeneratedImageResizeData = @@ -85,20 +86,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory(Skip = "Only for debugging and development")] [MemberData(nameof(KernelMapData))] - public void PrintNonNormalizedKernelMap(string resamplerName, int srcSize, int destSize) + public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize) + where TResampler : unmanaged, IResampler { - IResampler resampler = TestUtils.GetResampler(resamplerName); - - var kernelMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize, false); + var kernelMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize, false); this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); } [Theory] [MemberData(nameof(KernelMapData))] - public void KernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) + public void KernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) + where TResampler : unmanaged, IResampler { - this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize); + this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize); } // Comprehensive but expensive tests, for ResizeKernelMap. @@ -113,12 +114,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } #endif - private void VerifyKernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) + private void VerifyKernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) + where TResampler : unmanaged, IResampler { - IResampler resampler = TestUtils.GetResampler(resamplerName); - - var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize); - var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); + var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize); + var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); #if DEBUG this.Output.WriteLine(kernelMap.Info); @@ -153,20 +153,23 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } - private static string PrintKernelMap(ResizeKernelMap kernelMap) => - PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); + private static string PrintKernelMap(ResizeKernelMap kernelMap) + where TResampler : unmanaged, IResampler + => PrintKernelMap>(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); - private static string PrintKernelMap(ReferenceKernelMap kernelMap) => - PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); + private static string PrintKernelMap(ReferenceKernelMap kernelMap) + where TResampler : unmanaged, IResampler + => PrintKernelMap>(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); - private static string PrintKernelMap( + private static string PrintKernelMap( TKernelMap kernelMap, Func getDestinationSize, Func getKernel) + where TResampler : unmanaged, IResampler { var bld = new StringBuilder(); - if (kernelMap is ResizeKernelMap actualMap) + if (kernelMap is ResizeKernelMap actualMap) { bld.AppendLine(actualMap.Info); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 2cbffef47..63c93596f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -121,8 +121,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms configuration.MemoryAllocator = allocator; configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; - var verticalKernelMap = ResizeKernelMap.Calculate( - KnownResamplers.Bicubic, + var verticalKernelMap = ResizeKernelMap.Calculate( + default, destSize.Height, image0.Height, Configuration.Default.MemoryAllocator); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs index 33da33c71..db1e76ae5 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Pad(width, height); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index f87e17e06..e7b92b7b3 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -17,8 +17,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(width, height); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); } [Fact] @@ -30,8 +30,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(width, height, sampler); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); } @@ -47,8 +47,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(width, height, sampler, compand); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(compand, resizeProcessor.Compand); } @@ -73,8 +73,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(resizeOptions); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(compand, resizeProcessor.Compand); From 4e5d140c03cce13ada89141de2717af96513e694 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 21 Feb 2020 22:53:16 +1100 Subject: [PATCH 4/9] Update benchmarks --- .../ImageSharp.Benchmarks/Samplers/Diffuse.cs | 26 ----------- .../ImageSharp.Benchmarks/Samplers/Rotate.cs | 42 ++++++++---------- tests/ImageSharp.Benchmarks/Samplers/Skew.cs | 43 ++++++++----------- 3 files changed, 36 insertions(+), 75 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs index 096167eb9..e53661c73 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs @@ -34,32 +34,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers } } -// #### 25th October 2019 #### -// -// BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362 -// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK = 3.0.100 -// -// [Host] : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT -// Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.8.4018.0 -// Core : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT -// -// IterationCount=3 LaunchCount=1 WarmupCount=3 -// -// #### Before #### -// -// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------- |----- |-------- |----------:|---------:|---------:|------:|------:|------:|----------:| -// | DoDiffuse | Clr | Clr | 129.58 ms | 24.60 ms | 1.349 ms | - | - | - | 6 KB | -// | DoDiffuse | Core | Core | 92.63 ms | 89.78 ms | 4.921 ms | - | - | - | 4.58 KB | -// -// #### After #### -// -// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------- |----- |-------- |----------:|----------:|----------:|------:|------:|------:|----------:| -// | DoDiffuse | Clr | Clr | 124.93 ms | 33.297 ms | 1.8251 ms | - | - | - | 2 KB | -// | DoDiffuse | Core | Core | 89.63 ms | 9.895 ms | 0.5424 ms | - | - | - | 1.91 KB | - // #### 20th February 2020 #### // // BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs index e16e376fe..0610079fe 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs @@ -23,27 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers } } -/* - Nov 7 2018 -BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763 -Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores -.NET Core SDK = 2.1.403 - - [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 - Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - -LaunchCount=1 TargetCount=3 WarmupCount=3 - - #### BEFORE ####: - Method | Runtime | Mean | Error | StdDev | Allocated | ---------- |-------- |---------:|----------:|----------:|----------:| - DoRotate | Clr | 85.19 ms | 13.379 ms | 0.7560 ms | 6 KB | - DoRotate | Core | 53.51 ms | 9.512 ms | 0.5375 ms | 4.29 KB | - - #### AFTER ####: -Method | Runtime | Mean | Error | StdDev | Allocated | ---------- |-------- |---------:|---------:|---------:|----------:| - DoRotate | Clr | 77.08 ms | 23.97 ms | 1.354 ms | 6 KB | - DoRotate | Core | 40.36 ms | 47.43 ms | 2.680 ms | 4.36 KB | - */ +// #### 21th February 2020 #### +// +// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 +// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK = 3.1.101 +// +// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// Job-HOGSNT : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-FKDHXC : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-ODABAZ : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |--------- |-------------- |---------:|---------:|---------:|------:|------:|------:|----------:| +// | DoRotate | .NET 4.7.2 | 28.77 ms | 3.304 ms | 0.181 ms | - | - | - | 6.5 KB | +// | DoRotate | .NET Core 2.1 | 16.27 ms | 1.044 ms | 0.057 ms | - | - | - | 5.25 KB | +// | DoRotate | .NET Core 3.1 | 17.12 ms | 4.352 ms | 0.239 ms | - | - | - | 6.57 KB | diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs index 0ad27861b..7b8ec83a5 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -24,27 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers } } -/* - Nov 7 2018 -BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763 -Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores -.NET Core SDK = 2.1.403 - - [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 - Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - -LaunchCount=1 TargetCount=3 WarmupCount=3 - - #### BEFORE ####: -Method | Runtime | Mean | Error | StdDev | Allocated | -------- |-------- |---------:|---------:|----------:|----------:| - DoSkew | Clr | 78.14 ms | 8.383 ms | 0.4736 ms | 6 KB | - DoSkew | Core | 44.22 ms | 4.109 ms | 0.2322 ms | 4.28 KB | - - #### AFTER ####: -Method | Runtime | Mean | Error | StdDev | Allocated | -------- |-------- |---------:|----------:|----------:|----------:| - DoSkew | Clr | 71.63 ms | 25.589 ms | 1.4458 ms | 6 KB | - DoSkew | Core | 38.99 ms | 8.640 ms | 0.4882 ms | 4.36 KB | - */ +// #### 21th February 2020 #### +// +// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 +// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK = 3.1.101 +// +// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// Job-VKKTMF : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-KTVRKR : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-EONWDB : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |------- |-------------- |---------:|----------:|---------:|------:|------:|------:|----------:| +// | DoSkew | .NET 4.7.2 | 24.60 ms | 33.971 ms | 1.862 ms | - | - | - | 6.5 KB | +// | DoSkew | .NET Core 2.1 | 12.13 ms | 2.256 ms | 0.124 ms | - | - | - | 5.21 KB | +// | DoSkew | .NET Core 3.1 | 12.83 ms | 1.442 ms | 0.079 ms | - | - | - | 6.57 KB | From f374d194c50d243fa647566edc5c30c24837a0b0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 22 Feb 2020 00:05:50 +1100 Subject: [PATCH 5/9] Fix for tests, update reference images --- .../Processors/Transforms/AffineTransformProcessor.cs | 6 +++--- .../Transforms/AffineTransformProcessor{TPixel}.cs | 6 +++--- .../Processors/Transforms/ProjectiveTransformProcessor.cs | 6 +++--- .../Transforms/ProjectiveTransformProcessor{TPixel}.cs | 6 +++--- .../Processors/Transforms/ResamplerExtensions.Operations.cs | 4 ++-- tests/Images/External | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 849f06166..d0000edcf 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Guard.NotNull(sampler, nameof(sampler)); this.Sampler = sampler; this.TransformMatrix = matrix; - this.TargetDimensions = targetDimensions; + this.DestinationSize = targetDimensions; } /// @@ -35,9 +35,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public Matrix3x2 TransformMatrix { get; } /// - /// Gets the target dimensions to constrain the transformed image to. + /// Gets the destination size to constrain the transformed image to. /// - public Size TargetDimensions { get; } + public Size DestinationSize { get; } /// public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 9d01c049c..dddeba33f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class AffineTransformProcessor : TransformProcessor where TPixel : struct, IPixel { - private readonly Size targetSize; + private readonly Size destinationSize; private readonly Matrix3x2 transformMatrix; private readonly IResampler resampler; @@ -27,12 +27,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.targetSize = definition.TargetDimensions; + this.destinationSize = definition.DestinationSize; this.transformMatrix = definition.TransformMatrix; this.resampler = definition.Sampler; } - protected override Size GetDestinationSize() => this.targetSize; + protected override Size GetDestinationSize() => this.destinationSize; /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index d8a9c3ed9..6f17c1145 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Guard.NotNull(sampler, nameof(sampler)); this.Sampler = sampler; this.TransformMatrix = matrix; - this.TargetDimensions = targetDimensions; + this.DestinationSize = targetDimensions; } /// @@ -35,9 +35,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public Matrix4x4 TransformMatrix { get; } /// - /// Gets the target dimensions to constrain the transformed image to. + /// Gets the destination size to constrain the transformed image to. /// - public Size TargetDimensions { get; } + public Size DestinationSize { get; } /// public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index afc4658b4..6ab1e1358 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal class ProjectiveTransformProcessor : TransformProcessor where TPixel : struct, IPixel { - private readonly Size targetSize; + private readonly Size destinationSize; private readonly IResampler resampler; private readonly Matrix4x4 transformMatrix; @@ -27,12 +27,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public ProjectiveTransformProcessor(Configuration configuration, ProjectiveTransformProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.targetSize = definition.TargetDimensions; + this.destinationSize = definition.DestinationSize; this.transformMatrix = definition.TransformMatrix; this.resampler = definition.Sampler; } - protected override Size GetDestinationSize() => this.targetSize; + protected override Size GetDestinationSize() => this.destinationSize; /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs index 96fcc49f7..ec2aef9c5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { this.source = source; this.destination = destination; - this.bounds = destination.Bounds(); + this.bounds = source.Bounds(); this.matrix = matrix; this.maxX = destination.Width; } @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { this.source = source; this.destination = destination; - this.bounds = destination.Bounds(); + this.bounds = source.Bounds(); this.matrix = matrix; this.maxX = destination.Width; } diff --git a/tests/Images/External b/tests/Images/External index f9b4bfe42..f8a76fd3a 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit f9b4bfe42cacb3eefab02ada92ac771a9b93c080 +Subproject commit f8a76fd3a900b90c98df67ac896574383a4d09f3 From a0a54e2d9f8d9c37bff5e797b62490ac08219dd2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 23 Feb 2020 13:08:31 +1100 Subject: [PATCH 6/9] Make ResizeKernelMap non-generic. --- .../Transforms/Resize/ResamplerExtensions.cs | 13 ++++--- .../Transforms/Resize/ResizeKernel.cs | 4 +-- .../ResizeKernelMap.PeriodicKernelMap.cs | 15 ++++---- .../Transforms/Resize/ResizeKernelMap.cs | 34 ++++++++----------- .../Transforms/Resize/ResizeWorker.cs | 12 +++---- ...ResizeKernelMapTests.ReferenceKernelMap.cs | 8 ++--- .../Transforms/ResizeKernelMapTests.cs | 21 +++++------- .../Processors/Transforms/ResizeTests.cs | 2 +- 8 files changed, 48 insertions(+), 61 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs index b681a436c..2cd903924 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResamplerExtensions.cs @@ -78,13 +78,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // Since all image frame dimensions have to be the same we can calculate // the kernel maps and reuse for all frames. MemoryAllocator allocator = configuration.MemoryAllocator; - using var horizontalKernelMap = ResizeKernelMap.Calculate( + using var horizontalKernelMap = ResizeKernelMap.Calculate( in sampler, destinationRectangle.Width, sourceRectangle.Width, allocator); - using var verticalKernelMap = ResizeKernelMap.Calculate( + using var verticalKernelMap = ResizeKernelMap.Calculate( in sampler, destinationRectangle.Height, sourceRectangle.Height, @@ -135,17 +135,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms in operation); } - private static void ApplyResizeFrameTransform( + private static void ApplyResizeFrameTransform( Configuration configuration, ImageFrame source, ImageFrame destination, - ResizeKernelMap horizontalKernelMap, - ResizeKernelMap verticalKernelMap, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, Rectangle sourceRectangle, Rectangle destinationRectangle, Rectangle interest, bool compand) - where TResampler : unmanaged, IResampler where TPixel : struct, IPixel { PixelConversionModifiers conversionModifiers = @@ -155,7 +154,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // To reintroduce parallel processing, we would launch multiple workers // for different row intervals of the image. - using (var worker = new ResizeWorker( + using (var worker = new ResizeWorker( configuration, sourceArea, conversionModifiers, diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 83bee9111..f3521ebed 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Points to a collection of of weights allocated in . + /// Points to a collection of of weights allocated in . /// internal readonly unsafe struct ResizeKernel { @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } /// - /// Gets the span representing the portion of the that this window covers. + /// Gets the span representing the portion of the that this window covers. /// /// The . /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs index 52a308cf2..a79f60339 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs @@ -5,13 +5,12 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - internal partial class ResizeKernelMap - where TResampler : unmanaged, IResampler + internal partial class ResizeKernelMap { /// - /// Memory-optimized where repeating rows are stored only once. + /// Memory-optimized where repeating rows are stored only once. /// - private sealed class PeriodicKernelMap : ResizeKernelMap + private sealed class PeriodicKernelMap : ResizeKernelMap { private readonly int period; @@ -19,7 +18,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public PeriodicKernelMap( MemoryAllocator memoryAllocator, - TResampler sampler, int sourceLength, int destinationLength, double ratio, @@ -29,7 +27,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int cornerInterval) : base( memoryAllocator, - sampler, sourceLength, destinationLength, (cornerInterval * 2) + period, @@ -43,14 +40,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; - protected internal override void Initialize() + protected internal override void Initialize(in TResampler sampler) { // Build top corner data + one period of the mosaic data: int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; for (int i = 0; i < startOfFirstRepeatedMosaic; i++) { - this.kernels[i] = this.BuildKernel(i, i); + this.kernels[i] = this.BuildKernel(in sampler, i, i); } // Copy the mosaics: @@ -67,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int bottomStartData = this.cornerInterval + this.period; for (int i = 0; i < this.cornerInterval; i++) { - this.kernels[bottomStartDest + i] = this.BuildKernel(bottomStartDest + i, bottomStartData + i); + this.kernels[bottomStartDest + i] = this.BuildKernel(in sampler, bottomStartDest + i, bottomStartData + i); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 8432eb654..a6e6bf612 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -12,14 +12,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Provides resize kernel values from an optimized contiguous memory region. /// - /// The type of sampler. - internal partial class ResizeKernelMap : IDisposable - where TResampler : unmanaged, IResampler + internal partial class ResizeKernelMap : IDisposable { private static readonly TolerantMath TolerantMath = TolerantMath.Default; - private readonly TResampler sampler; - private readonly int sourceLength; private readonly double ratio; @@ -41,7 +37,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private ResizeKernelMap( MemoryAllocator memoryAllocator, - TResampler sampler, int sourceLength, int destinationLength, int bufferHeight, @@ -49,7 +44,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms double scale, int radius) { - this.sampler = sampler; this.ratio = ratio; this.scale = scale; this.radius = radius; @@ -79,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms $"radius:{this.radius}|sourceSize:{this.sourceLength}|destinationSize:{this.DestinationLength}|ratio:{this.ratio}|scale:{this.scale}"; /// - /// Disposes instance releasing it's backing buffer. + /// Disposes instance releasing it's backing buffer. /// public void Dispose() => this.Dispose(true); @@ -111,16 +105,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Computes the weights to apply at each pixel when resizing. /// + /// The type of sampler. /// The /// The destination size /// The source size /// The to use for buffer allocations - /// The - public static ResizeKernelMap Calculate( + /// The + public static ResizeKernelMap Calculate( in TResampler sampler, int destinationSize, int sourceSize, MemoryAllocator memoryAllocator) + where TResampler : unmanaged, IResampler { double ratio = (double)sourceSize / destinationSize; double scale = ratio; @@ -158,10 +154,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // If we don't have at least 2 periods, we go with the basic implementation: bool hasAtLeast2Periods = 2 * (cornerInterval + period) < destinationSize; - ResizeKernelMap result = hasAtLeast2Periods + ResizeKernelMap result = hasAtLeast2Periods ? new PeriodicKernelMap( memoryAllocator, - sampler, sourceSize, destinationSize, ratio, @@ -169,9 +164,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms radius, period, cornerInterval) - : new ResizeKernelMap( + : new ResizeKernelMap( memoryAllocator, - sampler, sourceSize, destinationSize, destinationSize, @@ -179,7 +173,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms scale, radius); - result.Initialize(); + result.Initialize(in sampler); return result; } @@ -187,11 +181,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Initializes the kernel map. /// - protected internal virtual void Initialize() + protected internal virtual void Initialize(in TResampler sampler) + where TResampler : unmanaged, IResampler { for (int i = 0; i < this.DestinationLength; i++) { - this.kernels[i] = this.BuildKernel(i, i); + this.kernels[i] = this.BuildKernel(in sampler, i, i); } } @@ -200,7 +195,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// referencing the data at row within , /// so the data reusable by other data rows. /// - private ResizeKernel BuildKernel(int destRowIndex, int dataRowIndex) + private ResizeKernel BuildKernel(in TResampler sampler, int destRowIndex, int dataRowIndex) + where TResampler : unmanaged, IResampler { double center = ((destRowIndex + .5) * this.ratio) - .5; @@ -224,7 +220,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int j = left; j <= right; j++) { - double value = this.sampler.GetValue((float)((j - center) / this.scale)); + double value = sampler.GetValue((float)((j - center) / this.scale)); sum += value; kernelValues[j - left] = value; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index cbec5242c..5ba204135 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -19,8 +18,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// When sliding the window, the contents of the bottom window band are copied to the new top band. /// For more details, and visual explanation, see "ResizeWorker.pptx". /// - internal sealed class ResizeWorker : IDisposable - where TResampler : unmanaged, IResampler + internal sealed class ResizeWorker : IDisposable where TPixel : struct, IPixel { private readonly Buffer2D transposedFirstPassBuffer; @@ -29,7 +27,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly PixelConversionModifiers conversionModifiers; - private readonly ResizeKernelMap horizontalKernelMap; + private readonly ResizeKernelMap horizontalKernelMap; private readonly BufferArea source; @@ -39,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly IMemoryOwner tempColumnBuffer; - private readonly ResizeKernelMap verticalKernelMap; + private readonly ResizeKernelMap verticalKernelMap; private readonly int destWidth; @@ -57,8 +55,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Configuration configuration, BufferArea source, PixelConversionModifiers conversionModifiers, - ResizeKernelMap horizontalKernelMap, - ResizeKernelMap verticalKernelMap, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, int destWidth, Rectangle targetWorkingRect, Point targetOrigin) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index d6fa07536..17477c83b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -13,8 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms /// /// Simplified reference implementation for functionality. /// - internal class ReferenceKernelMap - where TResampler : unmanaged, IResampler + internal class ReferenceKernelMap { private readonly ReferenceKernel[] kernels; @@ -27,7 +26,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; - public static ReferenceKernelMap Calculate(TResampler sampler, int destinationSize, int sourceSize, bool normalize = true) + public static ReferenceKernelMap Calculate(in TResampler sampler, int destinationSize, int sourceSize, bool normalize = true) + where TResampler : unmanaged, IResampler { double ratio = (double)sourceSize / destinationSize; double scale = ratio; @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms result.Add(new ReferenceKernel(left, floatVals)); } - return new ReferenceKernelMap(result.ToArray()); + return new ReferenceKernelMap(result.ToArray()); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs index 6ca3c3bee..e404c6460 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize) where TResampler : unmanaged, IResampler { - var kernelMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize, false); + var kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false); this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); } @@ -117,8 +117,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms private void VerifyKernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) where TResampler : unmanaged, IResampler { - var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize); - var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); + var referenceMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize); + var kernelMap = ResizeKernelMap.Calculate(in resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); #if DEBUG this.Output.WriteLine(kernelMap.Info); @@ -153,23 +153,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } - private static string PrintKernelMap(ResizeKernelMap kernelMap) - where TResampler : unmanaged, IResampler - => PrintKernelMap>(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); + private static string PrintKernelMap(ResizeKernelMap kernelMap) + => PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); - private static string PrintKernelMap(ReferenceKernelMap kernelMap) - where TResampler : unmanaged, IResampler - => PrintKernelMap>(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); + private static string PrintKernelMap(ReferenceKernelMap kernelMap) + => PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); - private static string PrintKernelMap( + private static string PrintKernelMap( TKernelMap kernelMap, Func getDestinationSize, Func getKernel) - where TResampler : unmanaged, IResampler { var bld = new StringBuilder(); - if (kernelMap is ResizeKernelMap actualMap) + if (kernelMap is ResizeKernelMap actualMap) { bld.AppendLine(actualMap.Info); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 63c93596f..7086bfeb3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms configuration.MemoryAllocator = allocator; configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; - var verticalKernelMap = ResizeKernelMap.Calculate( + var verticalKernelMap = ResizeKernelMap.Calculate( default, destSize.Height, image0.Height, From 6bac83838baf0f577da3920944582185a65610c4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 23 Feb 2020 17:22:48 +1100 Subject: [PATCH 7/9] Simplify Resamplers --- src/ImageSharp/Common/Helpers/ImageMaths.cs | 34 ---- src/ImageSharp/Processing/KnownResamplers.cs | 22 +-- .../Resamplers/CatmullRomResampler.cs | 77 --------- .../Transforms/Resamplers/CubicResampler.cs | 153 ++++++++++++++++++ .../Transforms/Resamplers/HermiteResampler.cs | 76 --------- .../Resamplers/Lanczos2Resampler.cs | 83 ---------- .../Resamplers/Lanczos3Resampler.cs | 83 ---------- .../Resamplers/Lanczos8Resampler.cs | 83 ---------- ...nczos5Resampler.cs => LanczosResampler.cs} | 38 ++++- .../Resamplers/MitchellNetravaliResampler.cs | 74 --------- .../Resamplers/RobidouxResampler.cs | 75 --------- .../Resamplers/RobidouxSharpResampler.cs | 75 --------- .../Transforms/Resamplers/SplineResampler.cs | 75 --------- 13 files changed, 196 insertions(+), 752 deletions(-) delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs create mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs rename src/ImageSharp/Processing/Processors/Transforms/Resamplers/{Lanczos5Resampler.cs => LanczosResampler.cs} (66%) delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index e7b14be42..a0ce30212 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -242,40 +242,6 @@ namespace SixLabors.ImageSharp return 1F; } - /// - /// Returns the result of a B-C filter against the given value. - /// - /// - /// The value to process. - /// The B-Spline curve variable. - /// The Cardinal curve variable. - /// - /// The . - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static float GetBcValue(float x, float b, float c) - { - if (x < 0F) - { - x = -x; - } - - float temp = x * x; - if (x < 1F) - { - x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); - return x / 6F; - } - - if (x < 2F) - { - x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); - return x / 6F; - } - - return 0F; - } - /// /// Gets the bounding from the given points. /// diff --git a/src/ImageSharp/Processing/KnownResamplers.cs b/src/ImageSharp/Processing/KnownResamplers.cs index 6c73513c8..348c08407 100644 --- a/src/ImageSharp/Processing/KnownResamplers.cs +++ b/src/ImageSharp/Processing/KnownResamplers.cs @@ -24,43 +24,43 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets the Catmull-Rom sampler, a well known standard Cubic Filter often used as a interpolation function /// - public static IResampler CatmullRom { get; } = default(CatmullRomResampler); + public static IResampler CatmullRom { get; } = CubicResampler.CatmullRom; /// /// Gets the Hermite sampler. A type of smoothed triangular interpolation filter that rounds off strong edges while /// preserving flat 'color levels' in the original image. /// - public static IResampler Hermite { get; } = default(HermiteResampler); + public static IResampler Hermite { get; } = CubicResampler.Hermite; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 2 pixels. /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos2 { get; } = default(Lanczos2Resampler); + public static IResampler Lanczos2 { get; } = LanczosResampler.Lanczos2; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 3 pixels /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos3 { get; } = default(Lanczos3Resampler); + public static IResampler Lanczos3 { get; } = LanczosResampler.Lanczos3; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 5 pixels /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos5 { get; } = default(Lanczos5Resampler); + public static IResampler Lanczos5 { get; } = LanczosResampler.Lanczos5; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 8 pixels /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos8 { get; } = default(Lanczos8Resampler); + public static IResampler Lanczos8 { get; } = LanczosResampler.Lanczos8; /// /// Gets the Mitchell-Netravali sampler. This seperable cubic algorithm yields a very good equilibrium between /// detail preservation (sharpness) and smoothness. /// - public static IResampler MitchellNetravali { get; } = default(MitchellNetravaliResampler); + public static IResampler MitchellNetravali { get; } = CubicResampler.MitchellNetravali; /// /// Gets the Nearest-Neighbour sampler that implements the nearest neighbor algorithm. This uses a very fast, unscaled filter @@ -72,17 +72,17 @@ namespace SixLabors.ImageSharp.Processing /// 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; } = default(RobidouxResampler); + public static IResampler Robidoux { get; } = CubicResampler.Robidoux; /// /// Gets the Robidoux Sharp sampler. A sharpened form of the sampler /// - public static IResampler RobidouxSharp { get; } = default(RobidouxSharpResampler); + public static IResampler RobidouxSharp { get; } = CubicResampler.RobidouxSharp; /// - /// Gets the Spline sampler. A seperable cubic algorithm similar to but yielding smoother results. + /// Gets the Spline sampler. A separable cubic algorithm similar to but yielding smoother results. /// - public static IResampler Spline { get; } = default(SplineResampler); + public static IResampler Spline { get; } = CubicResampler.Spline; /// /// Gets the Triangle sampler, otherwise known as Bilinear. This interpolation algorithm can be used where perfect image transformation diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs deleted file mode 100644 index f62b7d989..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs +++ /dev/null @@ -1,77 +0,0 @@ -// 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 Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. - /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large - /// scale image enlargements that a 'Lagrange' filter can produce. - /// - /// - public readonly struct CatmullRomResampler : IResampler - { - /// - public float Radius => 2; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) - { - const float B = 0; - const float C = 0.5F; - - return ImageMaths.GetBcValue(x, B, C); - } - - /// - [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); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs new file mode 100644 index 000000000..a8f3f0b63 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs @@ -0,0 +1,153 @@ +// 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 +{ + /// + /// Cubic filters contain a collection of different filters of varying B-Spline and + /// Cardinal values. With these two values you can generate any smoothly fitting + /// (continuious first derivative) piece-wise cubic filter. + /// + /// + /// + public readonly struct CubicResampler : IResampler + { + private readonly float bspline; + private readonly float cardinal; + + /// + /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. + /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large + /// scale image enlargements that a 'Lagrange' filter can produce. + /// + public static CubicResampler CatmullRom = new CubicResampler(2, 0, .5F); + + /// + /// The Hermite filter is type of smoothed triangular interpolation Filter, + /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. + /// + public static CubicResampler Hermite = new CubicResampler(2, 0, 0); + + /// + /// The function implements the Mitchell-Netravali algorithm as described on + /// Wikipedia + /// + public static CubicResampler MitchellNetravali = new CubicResampler(2, .3333333F, .3333333F); + + /// + /// The function implements the Robidoux algorithm. + /// + /// + public static CubicResampler Robidoux = new CubicResampler(2, .37821575509399867F, .31089212245300067F); + + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public static CubicResampler RobidouxSharp = new CubicResampler(2, .2620145123990142F, .3689927438004929F); + + /// + /// The function implements the spline algorithm. + /// + /// + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public static CubicResampler Spline = new CubicResampler(2, 1, 0); + + /// + /// Initializes a new instance of the struct. + /// + /// The sampling radius. + /// The B-Spline value. + /// The Cardinal cubic value. + public CubicResampler(float radius, float bspline, float cardinal) + { + this.Radius = radius; + this.bspline = bspline; + this.cardinal = cardinal; + } + + /// + public float Radius { get; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) + { + float b = this.bspline; + float c = this.cardinal; + + if (x < 0F) + { + x = -x; + } + + float temp = x * x; + if (x < 1F) + { + x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); + return x / 6F; + } + + if (x < 2F) + { + x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); + return x / 6F; + } + + return 0F; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void 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); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs deleted file mode 100644 index 883d4b684..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs +++ /dev/null @@ -1,76 +0,0 @@ -// 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 Hermite filter is type of smoothed triangular interpolation Filter, - /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. - /// - /// - public readonly struct HermiteResampler : IResampler - { - /// - public float Radius => 2; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) - { - const float B = 0F; - const float C = 0F; - - return ImageMaths.GetBcValue(x, B, C); - } - - /// - [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); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs deleted file mode 100644 index 93174a23a..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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 Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 2 pixels. - /// - public readonly struct Lanczos2Resampler : IResampler - { - /// - public float Radius => 2; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 2F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 2F); - } - - return 0F; - } - - /// - [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); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs deleted file mode 100644 index 6c008be63..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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 Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 3 pixels. - /// - public readonly struct Lanczos3Resampler : IResampler - { - /// - public float Radius => 3; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 3F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3F); - } - - return 0F; - } - - /// - [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); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs deleted file mode 100644 index d24c7a1f0..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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 Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 8 pixels. - /// - public readonly struct Lanczos8Resampler : IResampler - { - /// - public float Radius => 8; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 8F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8F); - } - - return 0F; - } - - /// - [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); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs similarity index 66% rename from src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs index 56da01fb2..4ed2d541c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs @@ -9,13 +9,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 5 pixels. + /// Wikipedia. /// - public readonly struct Lanczos5Resampler : IResampler + public readonly struct LanczosResampler : IResampler { + /// + /// Implements the Lanczos kernel algorithm with a radius of 2. + /// + public static LanczosResampler Lanczos2 = new LanczosResampler(2); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 3. + /// + public static LanczosResampler Lanczos3 = new LanczosResampler(3); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 5. + /// + public static LanczosResampler Lanczos5 = new LanczosResampler(5); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 8. + /// + public static LanczosResampler Lanczos8 = new LanczosResampler(8); + + /// + /// Initializes a new instance of the struct. + /// + /// The sampling radius. + public LanczosResampler(float radius) => this.Radius = radius; + /// - public float Radius => 5; + public float Radius { get; } /// [MethodImpl(InliningOptions.ShortMethod)] @@ -26,9 +51,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms x = -x; } - if (x < 5F) + float radius = this.Radius; + if (x < radius) { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5F); + return ImageMaths.SinC(x) * ImageMaths.SinC(x / radius); } return 0F; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs deleted file mode 100644 index e67e50ab0..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs +++ /dev/null @@ -1,74 +0,0 @@ -// 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 readonly struct MitchellNetravaliResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.3333333F; - const float C = 0.3333333F; - - return ImageMaths.GetBcValue(x, B, C); - } - - /// - [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); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs deleted file mode 100644 index 1b4d84e5a..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs +++ /dev/null @@ -1,75 +0,0 @@ -// 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 readonly struct RobidouxResampler : IResampler - { - /// - public float Radius => 2; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) - { - const float B = 0.37821575509399867F; - const float C = 0.31089212245300067F; - - return ImageMaths.GetBcValue(x, B, C); - } - - /// - [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); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs deleted file mode 100644 index 52991a649..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs +++ /dev/null @@ -1,75 +0,0 @@ -// 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 readonly struct RobidouxSharpResampler : IResampler - { - /// - public float Radius => 2; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) - { - const float B = 0.2620145123990142F; - const float C = 0.3689927438004929F; - - return ImageMaths.GetBcValue(x, B, C); - } - - /// - [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); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs deleted file mode 100644 index a21ed495b..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs +++ /dev/null @@ -1,75 +0,0 @@ -// 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 readonly struct SplineResampler : IResampler - { - /// - public float Radius => 2; - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public float GetValue(float x) - { - const float B = 1F; - const float C = 0F; - - return ImageMaths.GetBcValue(x, B, C); - } - - /// - [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); - } -} From de21e6d6145429e5cb99c1e94e0e635c62066127 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Feb 2020 00:06:30 +1100 Subject: [PATCH 8/9] Simplify IResampler contract --- src/ImageSharp/Common/Helpers/Guard.cs | 29 ++ .../AffineTransformProcessor.cs | 2 + ...neTransformProcessor{TPixel}.Transforms.cs | 209 ++++++++++++++ .../AffineTransformProcessor{TPixel}.cs | 20 +- .../{ => Automorphic}/AutoOrientProcessor.cs | 0 .../AutoOrientProcessor{TPixel}.cs | 0 .../AutomorphicTransformUtilities.cs | 104 +++++++ .../{ => Automorphic}/FlipProcessor.cs | 0 .../FlipProcessor{TPixel}.cs | 0 .../ProjectiveTransformProcessor.cs | 2 + ...veTransformProcessor{TPixel}.Transforms.cs | 209 ++++++++++++++ .../ProjectiveTransformProcessor{TPixel}.cs | 20 +- .../{ => Automorphic}/RotateProcessor.cs | 0 .../RotateProcessor{TPixel}.cs | 0 .../{ => Automorphic}/SkewProcessor.cs | 0 .../Processors/Transforms/IResampler.cs | 48 +--- .../IResamplingImageProcessor{TPixel}.cs | 23 ++ .../ResamplerExtensions.Operations.cs | 263 ------------------ .../Transforms/ResamplerExtensions.cs | 249 ----------------- .../Transforms/Resamplers/BicubicResampler.cs | 47 +--- .../Transforms/Resamplers/BoxResampler.cs | 47 +--- .../Transforms/Resamplers/CubicResampler.cs | 47 +--- .../Transforms/Resamplers/LanczosResampler.cs | 47 +--- .../Resamplers/NearestNeighborResampler.cs | 47 +--- .../Resamplers/TriangleResampler.cs | 47 +--- .../Transforms/Resamplers/WelchResampler.cs | 47 +--- .../Transforms/Resize/ResizeKernelMap.cs | 6 +- .../Transforms/Resize/ResizeProcessor.cs | 1 + ... => ResizeProcessor{TPixel}.Transforms.cs} | 22 +- .../Resize/ResizeProcessor{TPixel}.cs | 23 +- ...ResizeKernelMapTests.ReferenceKernelMap.cs | 2 +- .../Transforms/ResizeKernelMapTests.cs | 6 +- 32 files changed, 670 insertions(+), 897 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/Guard.cs rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/AffineTransformProcessor.cs (96%) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.Transforms.cs rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/AffineTransformProcessor{TPixel}.cs (72%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/AutoOrientProcessor.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/AutoOrientProcessor{TPixel}.cs (100%) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutomorphicTransformUtilities.cs rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/FlipProcessor.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/FlipProcessor{TPixel}.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/ProjectiveTransformProcessor.cs (96%) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.Transforms.cs rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/ProjectiveTransformProcessor{TPixel}.cs (72%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/RotateProcessor.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/RotateProcessor{TPixel}.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{ => Automorphic}/SkewProcessor.cs (100%) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/IResamplingImageProcessor{TPixel}.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.Operations.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/ResamplerExtensions.cs rename src/ImageSharp/Processing/Processors/Transforms/Resize/{ResamplerExtensions.cs => ResizeProcessor{TPixel}.Transforms.cs} (91%) diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs new file mode 100644 index 000000000..1d215d286 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp; + +namespace SixLabors +{ + internal static partial class Guard + { + /// + /// Ensures that the value is a value type. + /// + /// The target object, which cannot be null. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// is not a value type. + [MethodImpl(InliningOptions.ShortMethod)] + public static void MustBeValueType(TValue value, string parameterName) + { + if (!value.GetType().GetTypeInfo().IsValueType) + { + ThrowArgumentException("Type must be a struct.", parameterName); + } + } + } +} diff --git a/src/ImageSharp/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 d0000edcf..fec41dbff 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 000000000..3190857dd --- /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 dddeba33f..78310707c 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 000000000..b2283af01 --- /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 6f17c1145..f716ba701 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 000000000..c824bebae --- /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 6ab1e1358..8954d826f 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 c7557461a..616872f2a 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 000000000..cfa2df641 --- /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 ec2aef9c5..000000000 --- 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 245adb238..000000000 --- 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 5f9669f6f..2992bbf5a 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 ecaa28c80..98a789e34 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 a8f3f0b63..a6fba1b33 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 4ed2d541c..90d60e1b0 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 94b0b0405..20f0a9fb8 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 c8409e185..9cf9ae66a 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 673cbd5d7..a162c7411 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 a6e6bf612..3e7ccbd0a 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 520370b6e..4e6e7a48c 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 2cd903924..78f63ee0d 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 72064d0e3..5b69184d7 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 17477c83b..3d08cf1a4 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 e404c6460..8dbc05655 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); From 54fb7ab02d5e4ec4c3908589449ffe2ab3059278 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Feb 2020 13:44:03 +1100 Subject: [PATCH 9/9] Update based on feedback. --- .../AffineTransformProcessor{TPixel}.cs | 57 ----- .../ProjectiveTransformProcessor{TPixel}.cs | 57 ----- .../Processors/Transforms/IResampler.cs | 2 +- ...amplingTransformImageProcessor{TPixel}.cs} | 4 +- .../AffineTransformProcessor.cs | 0 .../AffineTransformProcessor{TPixel}.cs} | 65 +++-- .../AutoOrientProcessor.cs | 0 .../AutoOrientProcessor{TPixel}.cs | 0 .../{Automorphic => Linear}/FlipProcessor.cs | 0 .../FlipProcessor{TPixel}.cs | 0 .../LinearTransformUtilities.cs} | 2 +- .../ProjectiveTransformProcessor.cs | 0 .../ProjectiveTransformProcessor{TPixel}.cs} | 65 +++-- .../RotateProcessor.cs | 0 .../RotateProcessor{TPixel}.cs | 0 .../{Automorphic => Linear}/SkewProcessor.cs | 0 .../Transforms/Resamplers/BicubicResampler.cs | 2 +- .../Transforms/Resamplers/BoxResampler.cs | 2 +- .../Transforms/Resamplers/CubicResampler.cs | 2 +- .../Transforms/Resamplers/LanczosResampler.cs | 2 +- .../Resamplers/NearestNeighborResampler.cs | 2 +- .../Resamplers/TriangleResampler.cs | 2 +- .../Transforms/Resamplers/WelchResampler.cs | 2 +- .../ResizeProcessor{TPixel}.Transforms.cs | 222 ------------------ .../Resize/ResizeProcessor{TPixel}.cs | 204 +++++++++++++++- 25 files changed, 296 insertions(+), 396 deletions(-) delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.cs rename src/ImageSharp/Processing/Processors/Transforms/{IResamplingImageProcessor{TPixel}.cs => IResamplingTransformImageProcessor{TPixel}.cs} (87%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic => Linear}/AffineTransformProcessor.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic/AffineTransformProcessor{TPixel}.Transforms.cs => Linear/AffineTransformProcessor{TPixel}.cs} (75%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic => Linear}/AutoOrientProcessor.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic => Linear}/AutoOrientProcessor{TPixel}.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic => Linear}/FlipProcessor.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic => Linear}/FlipProcessor{TPixel}.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic/AutomorphicTransformUtilities.cs => Linear/LinearTransformUtilities.cs} (98%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic => Linear}/ProjectiveTransformProcessor.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic/ProjectiveTransformProcessor{TPixel}.Transforms.cs => Linear/ProjectiveTransformProcessor{TPixel}.cs} (75%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic => Linear}/RotateProcessor.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic => Linear}/RotateProcessor{TPixel}.cs (100%) rename src/ImageSharp/Processing/Processors/Transforms/{Automorphic => Linear}/SkewProcessor.cs (100%) delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.Transforms.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.cs deleted file mode 100644 index 78310707c..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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 -{ - /// - /// Provides the base methods to perform affine transforms on an image. - /// - /// The pixel format. - 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. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.destinationSize = definition.DestinationSize; - this.transformMatrix = definition.TransformMatrix; - this.resampler = definition.Sampler; - } - - protected override Size GetDestinationSize() => this.destinationSize; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - this.source = source; - this.destination = destination; - this.resampler.ApplyTransform(this); - } - - /// - public void ApplyTransform(in TResampler sampler) - where TResampler : struct, IResampler - => ApplyAffineTransform( - this.Configuration, - in sampler, - this.source, - this.destination, - this.transformMatrix); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.cs deleted file mode 100644 index 8954d826f..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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 -{ - /// - /// Provides the base methods to perform non-affine transforms on an image. - /// - /// The pixel format. - 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. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public ProjectiveTransformProcessor(Configuration configuration, ProjectiveTransformProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.destinationSize = definition.DestinationSize; - this.transformMatrix = definition.TransformMatrix; - this.resampler = definition.Sampler; - } - - protected override Size GetDestinationSize() => this.destinationSize; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - this.source = source; - this.destination = destination; - this.resampler.ApplyTransform(this); - } - - /// - public void ApplyTransform(in TResampler sampler) - where TResampler : struct, IResampler - => ApplyProjectiveTransform( - this.Configuration, - in sampler, - this.source, - this.destination, - this.transformMatrix); - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs index 616872f2a..c0c3be869 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. /// The transforming image processor. - void ApplyTransform(IResamplingImageProcessor processor) + void ApplyTransform(IResamplingTransformImageProcessor processor) where TPixel : struct, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResamplingImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs similarity index 87% rename from src/ImageSharp/Processing/Processors/Transforms/IResamplingImageProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs index cfa2df641..ab750f7fb 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResamplingImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs @@ -6,10 +6,10 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Implements an algorithm to alter the pixels of an image via a resampling transforms. + /// Implements an algorithm to alter the pixels of an image via resampling transforms. /// /// The pixel format. - public interface IResamplingImageProcessor : IImageProcessor + public interface IResamplingTransformImageProcessor : IImageProcessor where TPixel : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.Transforms.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs similarity index 75% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.Transforms.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 3190857dd..72bfa4c0b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AffineTransformProcessor{TPixel}.Transforms.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -11,28 +11,53 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - /// Contains the application code for performing an affine transform. - /// - internal partial class AffineTransformProcessor + /// + /// Provides the base methods to perform affine transforms on an image. + /// + /// The pixel format. + internal class AffineTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor + where TPixel : struct, IPixel { + private readonly Size destinationSize; + private readonly Matrix3x2 transformMatrix; + private readonly IResampler resampler; + private ImageFrame source; + private ImageFrame destination; + /// - /// Applies an affine transformation upon an image. + /// Initializes a new instance of the class. /// - /// 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) + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the processor parameters. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.destinationSize = definition.DestinationSize; + this.transformMatrix = definition.TransformMatrix; + this.resampler = definition.Sampler; + } + + protected override Size GetDestinationSize() => this.destinationSize; + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + { + this.source = source; + this.destination = destination; + this.resampler.ApplyTransform(this); + } + + /// + public void ApplyTransform(in TResampler sampler) where TResampler : struct, IResampler { + Configuration configuration = this.Configuration; + ImageFrame source = this.source; + ImageFrame destination = this.destination; + Matrix3x2 matrix = this.transformMatrix; + // Handle transforms that result in output identical to the original. if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) { @@ -55,8 +80,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return; } - int yRadius = AutomorphicTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); - int xRadius = AutomorphicTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); + int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); var radialExtents = new Vector2(xRadius, yRadius); int yLength = (yRadius * 2) + 1; int xLength = (xRadius * 2) + 1; @@ -186,7 +211,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // 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( + LinearTransformUtilities.Convolve( in this.sampler, point, sourceBuffer, diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutoOrientProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutoOrientProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/FlipProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/FlipProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutomorphicTransformUtilities.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs similarity index 98% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutomorphicTransformUtilities.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs index b2283af01..4fb1e27e0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/AutomorphicTransformUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Utility methods for affine and projective transforms. /// - internal static class AutomorphicTransformUtilities + internal static class LinearTransformUtilities { [MethodImpl(InliningOptions.ShortMethod)] internal static int GetSamplingRadius(in TResampler sampler, int sourceSize, int destinationSize) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.Transforms.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs similarity index 75% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.Transforms.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index c824bebae..b3315fa55 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/ProjectiveTransformProcessor{TPixel}.Transforms.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -11,28 +11,53 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - /// Contains the application code for performing a projective transform. - /// - internal partial class ProjectiveTransformProcessor + /// + /// Provides the base methods to perform non-affine transforms on an image. + /// + /// The pixel format. + internal class ProjectiveTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor + where TPixel : struct, IPixel { + private readonly Size destinationSize; + private readonly IResampler resampler; + private readonly Matrix4x4 transformMatrix; + private ImageFrame source; + private ImageFrame destination; + /// - /// Applies a projective transformation upon an image. + /// Initializes a new instance of the class. /// - /// 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) + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the processor parameters. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public ProjectiveTransformProcessor(Configuration configuration, ProjectiveTransformProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.destinationSize = definition.DestinationSize; + this.transformMatrix = definition.TransformMatrix; + this.resampler = definition.Sampler; + } + + protected override Size GetDestinationSize() => this.destinationSize; + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + { + this.source = source; + this.destination = destination; + this.resampler.ApplyTransform(this); + } + + /// + public void ApplyTransform(in TResampler sampler) where TResampler : struct, IResampler { + Configuration configuration = this.Configuration; + ImageFrame source = this.source; + ImageFrame destination = this.destination; + Matrix4x4 matrix = this.transformMatrix; + // Handle transforms that result in output identical to the original. if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) { @@ -55,8 +80,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return; } - int yRadius = AutomorphicTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); - int xRadius = AutomorphicTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); + int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); var radialExtents = new Vector2(xRadius, yRadius); int yLength = (yRadius * 2) + 1; int xLength = (xRadius * 2) + 1; @@ -186,7 +211,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // 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( + LinearTransformUtilities.Convolve( in this.sampler, point, sourceBuffer, diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/RotateProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/RotateProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Automorphic/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs similarity index 100% rename from src/ImageSharp/Processing/Processors/Transforms/Automorphic/SkewProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs index 2992bbf5a..085c81aad 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingImageProcessor processor) + public void ApplyTransform(IResamplingTransformImageProcessor 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 98a789e34..af2abb5f4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingImageProcessor processor) + public void ApplyTransform(IResamplingTransformImageProcessor 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 a6fba1b33..b39932674 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingImageProcessor processor) + public void ApplyTransform(IResamplingTransformImageProcessor 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 90d60e1b0..202edcd36 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingImageProcessor processor) + public void ApplyTransform(IResamplingTransformImageProcessor 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 20f0a9fb8..0bce3d129 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingImageProcessor processor) + public void ApplyTransform(IResamplingTransformImageProcessor 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 9cf9ae66a..42459d4a3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingImageProcessor processor) + public void ApplyTransform(IResamplingTransformImageProcessor 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 a162c7411..6142dbe06 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] - public void ApplyTransform(IResamplingImageProcessor processor) + public void ApplyTransform(IResamplingTransformImageProcessor processor) where TPixel : struct, IPixel => processor.ApplyTransform(in this); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.Transforms.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.Transforms.cs deleted file mode 100644 index 78f63ee0d..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.Transforms.cs +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Contains the application code for resizing. - /// - internal partial class ResizeProcessor - where TPixel : struct, IPixel - { - /// - /// Applies an resizing transformation upon an image. - /// - /// The type of sampler. - /// The configuration. - /// The pixel sampler. - /// The source image. - /// The destination image. - /// The source bounds. - /// The destination location. - /// Whether to compress or expand individual pixel color values on processing. - public static void ApplyResizeTransform( - Configuration configuration, - in TResampler sampler, - Image source, - Image destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - bool compand) - where TResampler : struct, IResampler - { - // Handle resize dimensions identical to the original - if (source.Width == destination.Width - && source.Height == destination.Height - && sourceRectangle == destinationRectangle) - { - for (int i = 0; i < source.Frames.Count; i++) - { - ImageFrame sourceFrame = source.Frames[i]; - ImageFrame destinationFrame = destination.Frames[i]; - - // The cloned will be blank here copy all the pixel data over - sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup()); - } - - return; - } - - var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds()); - - if (sampler is NearestNeighborResampler) - { - for (int i = 0; i < source.Frames.Count; i++) - { - ImageFrame sourceFrame = source.Frames[i]; - ImageFrame destinationFrame = destination.Frames[i]; - - ApplyNNResizeFrameTransform( - configuration, - sourceFrame, - destinationFrame, - sourceRectangle, - destinationRectangle, - interest); - } - - return; - } - - // Since all image frame dimensions have to be the same we can calculate - // the kernel maps and reuse for all frames. - MemoryAllocator allocator = configuration.MemoryAllocator; - using var horizontalKernelMap = ResizeKernelMap.Calculate( - in sampler, - destinationRectangle.Width, - sourceRectangle.Width, - allocator); - - using var verticalKernelMap = ResizeKernelMap.Calculate( - in sampler, - destinationRectangle.Height, - sourceRectangle.Height, - allocator); - - for (int i = 0; i < source.Frames.Count; i++) - { - ImageFrame sourceFrame = source.Frames[i]; - ImageFrame destinationFrame = destination.Frames[i]; - - ApplyResizeFrameTransform( - configuration, - sourceFrame, - destinationFrame, - horizontalKernelMap, - verticalKernelMap, - sourceRectangle, - destinationRectangle, - interest, - compand); - } - } - - private static void ApplyNNResizeFrameTransform( - Configuration configuration, - ImageFrame source, - ImageFrame destination, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - Rectangle interest) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)destinationRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height; - - var operation = new NNRowIntervalOperation( - sourceRectangle, - destinationRectangle, - widthFactor, - heightFactor, - source, - destination); - - ParallelRowIterator.IterateRows( - configuration, - interest, - in operation); - } - - private static void ApplyResizeFrameTransform( - Configuration configuration, - ImageFrame source, - ImageFrame destination, - ResizeKernelMap horizontalKernelMap, - ResizeKernelMap verticalKernelMap, - Rectangle sourceRectangle, - Rectangle destinationRectangle, - Rectangle interest, - bool compand) - { - PixelConversionModifiers conversionModifiers = - PixelConversionModifiers.Premultiply.ApplyCompanding(compand); - - BufferArea sourceArea = source.PixelBuffer.GetArea(sourceRectangle); - - // To reintroduce parallel processing, we would launch multiple workers - // for different row intervals of the image. - using (var worker = new ResizeWorker( - configuration, - sourceArea, - conversionModifiers, - horizontalKernelMap, - verticalKernelMap, - destination.Width, - interest, - destinationRectangle.Location)) - { - worker.Initialize(); - - var workingInterval = new RowInterval(interest.Top, interest.Bottom); - worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); - } - } - - private readonly struct NNRowIntervalOperation : IRowIntervalOperation - { - private readonly Rectangle sourceBounds; - private readonly Rectangle destinationBounds; - private readonly float widthFactor; - private readonly float heightFactor; - private readonly ImageFrame source; - private readonly ImageFrame destination; - - [MethodImpl(InliningOptions.ShortMethod)] - public NNRowIntervalOperation( - Rectangle sourceBounds, - Rectangle destinationBounds, - float widthFactor, - float heightFactor, - ImageFrame source, - ImageFrame destination) - { - this.sourceBounds = sourceBounds; - this.destinationBounds = destinationBounds; - this.widthFactor = widthFactor; - this.heightFactor = heightFactor; - this.source = source; - this.destination = destination; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(in RowInterval rows) - { - int sourceX = this.sourceBounds.X; - int sourceY = this.sourceBounds.Y; - int destX = this.destinationBounds.X; - int destY = this.destinationBounds.Y; - int destLeft = this.destinationBounds.Left; - int destRight = this.destinationBounds.Right; - - for (int y = rows.Min; y < rows.Max; y++) - { - // Y coordinates of source points - Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); - Span targetRow = this.destination.GetPixelRowSpan(y); - - for (int x = destLeft; x < destRight; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; - } - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 5b69184d7..1a6b8030d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -9,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Implements resizing of images using various resamplers. /// /// The pixel format. - internal partial class ResizeProcessor : TransformProcessor, IResamplingImageProcessor + internal partial class ResizeProcessor : TransformProcessor, IResamplingTransformImageProcessor where TPixel : struct, IPixel { private readonly int destinationWidth; @@ -48,14 +52,196 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } public void ApplyTransform(in TResampler sampler) - where TResampler : struct, IResampler => - ApplyResizeTransform( - this.Configuration, + where TResampler : struct, IResampler + { + Configuration configuration = this.Configuration; + Image source = this.Source; + Image destination = this.destination; + Rectangle sourceRectangle = this.SourceRectangle; + Rectangle destinationRectangle = this.destinationRectangle; + bool compand = this.compand; + + // Handle resize dimensions identical to the original + if (source.Width == destination.Width + && source.Height == destination.Height + && sourceRectangle == destinationRectangle) + { + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; + + // The cloned will be blank here copy all the pixel data over + sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup()); + } + + return; + } + + var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds()); + + if (sampler is NearestNeighborResampler) + { + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; + + ApplyNNResizeFrameTransform( + configuration, + sourceFrame, + destinationFrame, + sourceRectangle, + destinationRectangle, + interest); + } + + return; + } + + // Since all image frame dimensions have to be the same we can calculate + // the kernel maps and reuse for all frames. + MemoryAllocator allocator = configuration.MemoryAllocator; + using var horizontalKernelMap = ResizeKernelMap.Calculate( + in sampler, + destinationRectangle.Width, + sourceRectangle.Width, + allocator); + + using var verticalKernelMap = ResizeKernelMap.Calculate( in sampler, - this.Source, - this.destination, - this.SourceRectangle, - this.destinationRectangle, - this.compand); + destinationRectangle.Height, + sourceRectangle.Height, + allocator); + + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; + + ApplyResizeFrameTransform( + configuration, + sourceFrame, + destinationFrame, + horizontalKernelMap, + verticalKernelMap, + sourceRectangle, + destinationRectangle, + interest, + compand); + } + } + + private static void ApplyNNResizeFrameTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Rectangle sourceRectangle, + Rectangle destinationRectangle, + Rectangle interest) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)destinationRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height; + + var operation = new NNRowIntervalOperation( + sourceRectangle, + destinationRectangle, + widthFactor, + heightFactor, + source, + destination); + + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } + + private static void ApplyResizeFrameTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, + Rectangle sourceRectangle, + Rectangle destinationRectangle, + Rectangle interest, + bool compand) + { + PixelConversionModifiers conversionModifiers = + PixelConversionModifiers.Premultiply.ApplyCompanding(compand); + + BufferArea sourceArea = source.PixelBuffer.GetArea(sourceRectangle); + + // To reintroduce parallel processing, we would launch multiple workers + // for different row intervals of the image. + using (var worker = new ResizeWorker( + configuration, + sourceArea, + conversionModifiers, + horizontalKernelMap, + verticalKernelMap, + destination.Width, + interest, + destinationRectangle.Location)) + { + worker.Initialize(); + + var workingInterval = new RowInterval(interest.Top, interest.Bottom); + worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); + } + } + + private readonly struct NNRowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle sourceBounds; + private readonly Rectangle destinationBounds; + private readonly float widthFactor; + private readonly float heightFactor; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNRowIntervalOperation( + Rectangle sourceBounds, + Rectangle destinationBounds, + float widthFactor, + float heightFactor, + ImageFrame source, + ImageFrame destination) + { + this.sourceBounds = sourceBounds; + this.destinationBounds = destinationBounds; + this.widthFactor = widthFactor; + this.heightFactor = heightFactor; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int sourceX = this.sourceBounds.X; + int sourceY = this.sourceBounds.Y; + int destX = this.destinationBounds.X; + int destY = this.destinationBounds.Y; + int destLeft = this.destinationBounds.Left; + int destRight = this.destinationBounds.Right; + + for (int y = rows.Min; y < rows.Max; y++) + { + // Y coordinates of source points + Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.GetPixelRowSpan(y); + + for (int x = destLeft; x < destRight; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; + } + } + } + } } }