From a2f0d24893f2ff704b451e2af8f5f4bbcf1ff885 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 19 Jan 2018 21:44:18 +1100 Subject: [PATCH] Add internal non-affine transform methods --- ImageSharp.sln.DotSettings | 10 + ...ocessor.cs => AffineTransformProcessor.cs} | 138 ++-------- ...cs => CenteredAffineTransformProcessor.cs} | 10 +- .../CenteredNonAffineTransformProcessor.cs | 43 ++++ .../InterpolatedTransformProcessorBase.cs | 139 ++++++++++ .../Transforms/NonAffineTransformProcessor.cs | 238 ++++++++++++++++++ .../Processors/Transforms/RotateProcessor.cs | 2 +- .../Processors/Transforms/SkewProcessor.cs | 2 +- .../Transforms/TransformProcessor.cs | 47 ---- .../Processing/Transforms/Transform.cs | 46 +++- .../Processing/Transforms/TransformHelpers.cs | 29 ++- 11 files changed, 523 insertions(+), 181 deletions(-) rename src/ImageSharp/Processing/Processors/Transforms/{AffineProcessor.cs => AffineTransformProcessor.cs} (67%) rename src/ImageSharp/Processing/Processors/Transforms/{CenteredAffineProcessor.cs => CenteredAffineTransformProcessor.cs} (77%) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/CenteredNonAffineTransformProcessor.cs create mode 100644 src/ImageSharp/Processing/Processors/Transforms/InterpolatedTransformProcessorBase.cs create mode 100644 src/ImageSharp/Processing/Processors/Transforms/NonAffineTransformProcessor.cs delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs diff --git a/ImageSharp.sln.DotSettings b/ImageSharp.sln.DotSettings index 1839bf2f8..435aad73b 100644 --- a/ImageSharp.sln.DotSettings +++ b/ImageSharp.sln.DotSettings @@ -38,10 +38,15 @@ NEXT_LINE_SHIFTED_2 1 1 + False + False False + NEVER False False + NEVER False + ALWAYS False True ON_SINGLE_LINE @@ -370,8 +375,13 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True True True + True True True \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs similarity index 67% rename from src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 59b844263..ce4fbdd71 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -5,12 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Numerics; -using System.Runtime.CompilerServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -20,35 +18,42 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Provides the base methods to perform affine transforms on an image. /// /// The pixel format. - internal abstract class AffineProcessor : CloningImageProcessor + internal class AffineTransformProcessor : InterpolatedTransformProcessorBase where TPixel : struct, IPixel { private Rectangle targetRectangle; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. + /// + /// The transform matrix + public AffineTransformProcessor(Matrix3x2 matrix) + : this(matrix, KnownResamplers.Bicubic) + { + } + + /// + /// Initializes a new instance of the class. /// /// The transform matrix /// The sampler to perform the transform operation. - protected AffineProcessor(Matrix3x2 matrix, IResampler sampler) + public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler) : this(matrix, sampler, Rectangle.Empty) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The transform matrix /// The sampler to perform the transform operation. /// The rectangle to constrain the transformed image to. - protected AffineProcessor(Matrix3x2 matrix, IResampler sampler, Rectangle rectangle) + public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Rectangle rectangle) + : base(sampler) { // Tansforms are inverted else the output is the opposite of the expected. Matrix3x2.Invert(matrix, out matrix); this.TransformMatrix = matrix; - - this.Sampler = sampler; - this.targetRectangle = rectangle; } @@ -57,11 +62,6 @@ namespace SixLabors.ImageSharp.Processing.Processors /// public Matrix3x2 TransformMatrix { get; } - /// - /// Gets the sampler to perform interpolation of the transform operation. - /// - public IResampler Sampler { get; } - /// protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int width = this.targetRectangle.Width; Rectangle sourceBounds = source.Bounds(); - // Since could potentially be resizing the canvas we need to re-center the matrix + // Since could potentially be resizing the canvas we might need to re-calculate the matrix Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, this.targetRectangle); if (this.Sampler is NearestNeighborResampler) @@ -211,26 +211,6 @@ namespace SixLabors.ImageSharp.Processing.Processors } } - /// - protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) - { - ExifProfile profile = destination.MetaData.ExifProfile; - if (profile == null) - { - return; - } - - if (profile.GetValue(ExifTag.PixelXDimension) != null) - { - profile.SetValue(ExifTag.PixelXDimension, destination.Width); - } - - if (profile.GetValue(ExifTag.PixelYDimension) != null) - { - profile.SetValue(ExifTag.PixelYDimension, destination.Height); - } - } - /// /// Gets a transform matrix adjusted for final processing based upon the target image bounds. /// @@ -254,91 +234,5 @@ namespace SixLabors.ImageSharp.Processing.Processors { return sourceRectangle; } - - /// - /// Calculated the weights for the given point. - /// This method uses more samples than the upscaled version to ensure edge pixels are correctly rendered. - /// Additionally the weights are nomalized. - /// - /// The minimum sampling offset - /// The maximum sampling offset - /// The minimum source bounds - /// The maximum source bounds - /// The transformed point dimension - /// The sampler - /// The transformed image scale relative to the source - /// The collection of weights - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CalculateWeightsDown(int min, int max, int sourceMin, int sourceMax, float point, IResampler sampler, float scale, Span weights) - { - float sum = 0; - ref float weightsBaseRef = ref weights[0]; - - // Downsampling weights requires more edge sampling plus normalization of the weights - for (int x = 0, i = min; i <= max; i++, x++) - { - int index = i; - if (index < sourceMin) - { - index = sourceMin; - } - - if (index > sourceMax) - { - index = sourceMax; - } - - float weight = sampler.GetValue((index - point) / scale); - sum += weight; - Unsafe.Add(ref weightsBaseRef, x) = weight; - } - - if (sum > 0) - { - for (int i = 0; i < weights.Length; i++) - { - ref float wRef = ref Unsafe.Add(ref weightsBaseRef, i); - wRef = wRef / sum; - } - } - } - - /// - /// Calculated the weights for the given point. - /// - /// The minimum source bounds - /// The maximum source bounds - /// The transformed point dimension - /// The sampler - /// The collection of weights - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CalculateWeightsScaleUp(int sourceMin, int sourceMax, float point, IResampler sampler, Span weights) - { - ref float weightsBaseRef = ref weights[0]; - for (int x = 0, i = sourceMin; i <= sourceMax; i++, x++) - { - float weight = sampler.GetValue(i - point); - Unsafe.Add(ref weightsBaseRef, x) = weight; - } - } - - /// - /// Calculates the sampling radius for the current sampler - /// - /// The source dimension size - /// The destination dimension size - /// The radius, and scaling factor - private (float radius, float scale, float ratio) GetSamplingRadius(int sourceSize, int destinationSize) - { - float ratio = (float)sourceSize / destinationSize; - float scale = ratio; - - if (scale < 1F) - { - scale = 1F; - } - - return (MathF.Ceiling(scale * this.Sampler.Radius), scale, ratio); - } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs similarity index 77% rename from src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs index 5631af3aa..34eabba9b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs @@ -7,15 +7,19 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { - internal abstract class CenteredAffineProcessor : AffineProcessor + /// + /// A base class that provides methods to allow the automatic centering of affine transforms + /// + /// The pixel format. + internal abstract class CenteredAffineTransformProcessor : AffineTransformProcessor where TPixel : struct, IPixel { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The transform matrix /// The sampler to perform the transform operation. - protected CenteredAffineProcessor(Matrix3x2 matrix, IResampler sampler) + protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler) : base(matrix, sampler) { } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredNonAffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredNonAffineTransformProcessor.cs new file mode 100644 index 000000000..bb1505c80 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/CenteredNonAffineTransformProcessor.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// A base class that provides methods to allow the automatic centering of non-affine transforms + /// + /// The pixel format. + internal abstract class CenteredNonAffineTransformProcessor : NonAffineTransformProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The transform matrix + /// The sampler to perform the transform operation. + protected CenteredNonAffineTransformProcessor(Matrix4x4 matrix, IResampler sampler) + : base(matrix, sampler) + { + } + + /// + protected override Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) + { + var translationToTargetCenter = Matrix4x4.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F, 0); + var translateToSourceCenter = Matrix4x4.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F, 0); + return translationToTargetCenter * this.TransformMatrix * translateToSourceCenter; + } + + /// + protected override Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix) + { + return Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 sizeMatrix) + ? TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, sizeMatrix) + : sourceRectangle; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/InterpolatedTransformProcessorBase.cs b/src/ImageSharp/Processing/Processors/Transforms/InterpolatedTransformProcessorBase.cs new file mode 100644 index 000000000..5c32f044a --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/InterpolatedTransformProcessorBase.cs @@ -0,0 +1,139 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// The base class for performing interpolated affine and non-affine transforms. + /// + /// The pixel format. + internal abstract class InterpolatedTransformProcessorBase : CloningImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The sampler to perform the transform operation. + protected InterpolatedTransformProcessorBase(IResampler sampler) + { + this.Sampler = sampler; + } + + /// + /// Gets the sampler to perform interpolation of the transform operation. + /// + public IResampler Sampler { get; } + + /// + /// Calculated the weights for the given point. + /// This method uses more samples than the upscaled version to ensure edge pixels are correctly rendered. + /// Additionally the weights are nomalized. + /// + /// The minimum sampling offset + /// The maximum sampling offset + /// The minimum source bounds + /// The maximum source bounds + /// The transformed point dimension + /// The sampler + /// The transformed image scale relative to the source + /// The collection of weights + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static void CalculateWeightsDown(int min, int max, int sourceMin, int sourceMax, float point, IResampler sampler, float scale, Span weights) + { + float sum = 0; + ref float weightsBaseRef = ref weights[0]; + + // Downsampling weights requires more edge sampling plus normalization of the weights + for (int x = 0, i = min; i <= max; i++, x++) + { + int index = i; + if (index < sourceMin) + { + index = sourceMin; + } + + if (index > sourceMax) + { + index = sourceMax; + } + + float weight = sampler.GetValue((index - point) / scale); + sum += weight; + Unsafe.Add(ref weightsBaseRef, x) = weight; + } + + if (sum > 0) + { + for (int i = 0; i < weights.Length; i++) + { + ref float wRef = ref Unsafe.Add(ref weightsBaseRef, i); + wRef = wRef / sum; + } + } + } + + /// + /// Calculated the weights for the given point. + /// + /// The minimum source bounds + /// The maximum source bounds + /// The transformed point dimension + /// The sampler + /// The collection of weights + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static void CalculateWeightsScaleUp(int sourceMin, int sourceMax, float point, IResampler sampler, Span weights) + { + ref float weightsBaseRef = ref weights[0]; + for (int x = 0, i = sourceMin; i <= sourceMax; i++, x++) + { + float weight = sampler.GetValue(i - point); + Unsafe.Add(ref weightsBaseRef, x) = weight; + } + } + + /// + /// Calculates the sampling radius for the current sampler + /// + /// The source dimension size + /// The destination dimension size + /// The radius, and scaling factor + protected (float radius, float scale, float ratio) GetSamplingRadius(int sourceSize, int destinationSize) + { + float ratio = (float)sourceSize / destinationSize; + float scale = ratio; + + if (scale < 1F) + { + scale = 1F; + } + + return (MathF.Ceiling(scale * this.Sampler.Radius), scale, ratio); + } + + /// + protected override void AfterImageApply(Image source, Image destination, Rectangle sourceRectangle) + { + ExifProfile profile = destination.MetaData.ExifProfile; + if (profile == null) + { + return; + } + + if (profile.GetValue(ExifTag.PixelXDimension) != null) + { + profile.SetValue(ExifTag.PixelXDimension, destination.Width); + } + + if (profile.GetValue(ExifTag.PixelYDimension) != null) + { + profile.SetValue(ExifTag.PixelYDimension, destination.Height); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/NonAffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/NonAffineTransformProcessor.cs new file mode 100644 index 000000000..caaf812b0 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/NonAffineTransformProcessor.cs @@ -0,0 +1,238 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Provides the base methods to perform non-affine transforms on an image. + /// + /// The pixel format. + internal class NonAffineTransformProcessor : InterpolatedTransformProcessorBase + where TPixel : struct, IPixel + { + private Rectangle targetRectangle; + + /// + /// Initializes a new instance of the class. + /// + /// The transform matrix + public NonAffineTransformProcessor(Matrix4x4 matrix) + : this(matrix, KnownResamplers.Bicubic) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The transform matrix + /// The sampler to perform the transform operation. + public NonAffineTransformProcessor(Matrix4x4 matrix, IResampler sampler) + : this(matrix, sampler, Rectangle.Empty) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The transform matrix + /// The sampler to perform the transform operation. + /// The rectangle to constrain the transformed image to. + public NonAffineTransformProcessor(Matrix4x4 matrix, IResampler sampler, Rectangle rectangle) + : base(sampler) + { + // Tansforms are inverted else the output is the opposite of the expected. + Matrix4x4.Invert(matrix, out matrix); + this.TransformMatrix = matrix; + this.targetRectangle = rectangle; + } + + /// + /// Gets the matrix used to supply the non-affine transform + /// + public Matrix4x4 TransformMatrix { get; } + + /// + protected override Image CreateDestination(Image source, Rectangle sourceRectangle) + { + if (this.targetRectangle == Rectangle.Empty) + { + this.targetRectangle = this.GetTransformedBoundingRectangle(sourceRectangle, this.TransformMatrix); + } + + // We will always be creating the clone even for mutate because we may need to resize the canvas + IEnumerable> frames = + source.Frames.Select(x => new ImageFrame(this.targetRectangle.Width, this.targetRectangle.Height, x.MetaData.Clone())); + + // Use the overload to prevent an extra frame being added + return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); + } + + /// + protected override void OnApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + int height = this.targetRectangle.Height; + int width = this.targetRectangle.Width; + Rectangle sourceBounds = source.Bounds(); + + // Since could potentially be resizing the canvas we might need to re-calculate the matrix + Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, this.targetRectangle); + + if (this.Sampler is NearestNeighborResampler) + { + Parallel.For( + 0, + height, + configuration.ParallelOptions, + y => + { + Span destRow = destination.GetPixelRowSpan(y); + + for (int x = 0; x < width; x++) + { + var point = Point.Round(Vector2.Transform(new Vector2(x, y), matrix)); + if (sourceBounds.Contains(point.X, point.Y)) + { + destRow[x] = source[point.X, point.Y]; + } + } + }); + + return; + } + + int maxSourceX = source.Width - 1; + int maxSourceY = source.Height - 1; + (float radius, float scale, float ratio) xRadiusScale = this.GetSamplingRadius(source.Width, destination.Width); + (float radius, float scale, float ratio) yRadiusScale = this.GetSamplingRadius(source.Height, destination.Height); + float xScale = xRadiusScale.scale; + float yScale = yRadiusScale.scale; + var radius = new Vector2(xRadiusScale.radius, yRadiusScale.radius); + IResampler sampler = this.Sampler; + var maxSource = new Vector4(maxSourceX, maxSourceY, maxSourceX, maxSourceY); + int xLength = (int)MathF.Ceiling((radius.X * 2) + 2); + int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2); + + using (var yBuffer = new Buffer2D(yLength, height)) + using (var xBuffer = new Buffer2D(xLength, height)) + { + Parallel.For( + 0, + height, + configuration.ParallelOptions, + y => + { + Span destRow = destination.GetPixelRowSpan(y); + Span ySpan = yBuffer.GetRowSpan(y); + Span xSpan = xBuffer.GetRowSpan(y); + + for (int x = 0; x < width; 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), matrix); + + // Clamp sampling pixel radial extents to the source image edges + Vector2 maxXY = point + radius; + Vector2 minXY = point - radius; + + // max, maxY, minX, minY + var extents = new Vector4( + MathF.Floor(maxXY.X + .5F), + MathF.Floor(maxXY.Y + .5F), + MathF.Ceiling(minXY.X - .5F), + MathF.Ceiling(minXY.Y - .5F)); + + int right = (int)extents.X; + int bottom = (int)extents.Y; + int left = (int)extents.Z; + int top = (int)extents.W; + + extents = Vector4.Clamp(extents, Vector4.Zero, maxSource); + + int maxX = (int)extents.X; + int maxY = (int)extents.Y; + int minX = (int)extents.Z; + int minY = (int)extents.W; + + if (minX == maxX || minY == maxY) + { + continue; + } + + // It appears these have to be calculated on-the-fly. + // Precalulating transformed weights would require prior knowledge of every transformed pixel location + // since they can be at sub-pixel positions on both axis. + // I've optimized where I can but am always open to suggestions. + if (yScale > 1 && xScale > 1) + { + CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ySpan); + CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, xSpan); + } + else + { + CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ySpan); + CalculateWeightsScaleUp(minX, maxX, point.X, sampler, xSpan); + } + + // Now multiply the results against the offsets + Vector4 sum = Vector4.Zero; + for (int yy = 0, j = minY; j <= maxY; j++, yy++) + { + float yWeight = ySpan[yy]; + + for (int xx = 0, i = minX; i <= maxX; i++, xx++) + { + float xWeight = xSpan[xx]; + var vector = source[i, j].ToVector4(); + + // Values are first premultiplied to prevent darkening of edge pixels + var mupltiplied = new Vector4(new Vector3(vector.X, vector.Y, vector.Z) * vector.W, vector.W); + sum += mupltiplied * xWeight * yWeight; + } + } + + ref TPixel dest = ref destRow[x]; + + // Reverse the premultiplication + dest.PackFromVector4(new Vector4(new Vector3(sum.X, sum.Y, sum.Z) / sum.W, sum.W)); + } + }); + } + } + + /// + /// Gets a transform matrix adjusted for final processing based upon the target image bounds. + /// + /// The source image bounds. + /// The destination image bounds. + /// + /// The . + /// + protected virtual Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) + { + return this.TransformMatrix; + } + + /// + /// Gets the bounding relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// The + protected virtual Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix) + { + return sourceRectangle; + } + } +} \ 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 fa47dadaa..b93c86915 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Provides methods that allow the rotating of images. /// /// The pixel format. - internal class RotateProcessor : CenteredAffineProcessor + internal class RotateProcessor : CenteredAffineTransformProcessor where TPixel : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index b123a309b..8c47b3527 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Provides methods that allow the skewing of images. /// /// The pixel format. - internal class SkewProcessor : CenteredAffineProcessor + internal class SkewProcessor : CenteredAffineTransformProcessor where TPixel : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs deleted file mode 100644 index 140fd7ec6..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors -{ - /// - /// Provides methods that allow the tranformation of images using various algorithms. - /// - /// The pixel format. - internal class TransformProcessor : AffineProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The transformation matrix - public TransformProcessor(Matrix3x2 matrix) - : this(matrix, KnownResamplers.Bicubic) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The transformation matrix - /// The sampler to perform the transform operation. - public TransformProcessor(Matrix3x2 matrix, IResampler sampler) - : base(matrix, sampler) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix - /// The sampler to perform the transform operation. - /// The rectangle to constrain the transformed image to. - public TransformProcessor(Matrix3x2 matrix, IResampler sampler, Rectangle rectangle) - : base(matrix, sampler, rectangle) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Transform.cs b/src/ImageSharp/Processing/Transforms/Transform.cs index 74f91fa70..cbfe6da18 100644 --- a/src/ImageSharp/Processing/Transforms/Transform.cs +++ b/src/ImageSharp/Processing/Transforms/Transform.cs @@ -18,18 +18,18 @@ namespace SixLabors.ImageSharp /// Transforms an image by the given matrix. /// /// The pixel format. - /// The image to skew. + /// The image to transform. /// The transformation matrix. /// The public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix) where TPixel : struct, IPixel - => Transform(source, matrix, KnownResamplers.NearestNeighbor); + => Transform(source, matrix, KnownResamplers.NearestNeighbor); /// /// Transforms an image by the given matrix using the specified sampling algorithm. /// /// The pixel format. - /// The image to skew. + /// The image to transform. /// The transformation matrix. /// The to perform the resampling. /// The @@ -41,13 +41,49 @@ namespace SixLabors.ImageSharp /// Transforms an image by the given matrix using the specified sampling algorithm. /// /// The pixel format. - /// The image to skew. + /// The image to transform. /// The transformation matrix. /// The to perform the resampling. /// The rectangle to constrain the transformed image to. /// The public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix, IResampler sampler, Rectangle rectangle) where TPixel : struct, IPixel - => source.ApplyProcessor(new TransformProcessor(matrix, sampler, rectangle)); + => source.ApplyProcessor(new AffineTransformProcessor(matrix, sampler, rectangle)); + + /// + /// Transforms an image by the given matrix. + /// + /// The pixel format. + /// The image to transform. + /// The transformation matrix. + /// The + internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix) + where TPixel : struct, IPixel + => Transform(source, matrix, KnownResamplers.NearestNeighbor); + + /// + /// Transforms an image by the given matrix using the specified sampling algorithm. + /// + /// The pixel format. + /// The image to transform. + /// The transformation matrix. + /// The to perform the resampling. + /// The + internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix, IResampler sampler) + where TPixel : struct, IPixel + => Transform(source, matrix, sampler, Rectangle.Empty); + + /// + /// Transforms an image by the given matrix using the specified sampling algorithm. + /// + /// The pixel format. + /// The image to transform. + /// The transformation matrix. + /// The to perform the resampling. + /// The rectangle to constrain the transformed image to. + /// The + internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix, IResampler sampler, Rectangle rectangle) + where TPixel : struct, IPixel + => source.ApplyProcessor(new NonAffineTransformProcessor(matrix, sampler, rectangle)); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/TransformHelpers.cs b/src/ImageSharp/Processing/Transforms/TransformHelpers.cs index 419c1c13d..119fc9eed 100644 --- a/src/ImageSharp/Processing/Transforms/TransformHelpers.cs +++ b/src/ImageSharp/Processing/Transforms/TransformHelpers.cs @@ -8,7 +8,7 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp { /// - /// Contains helper methods for working with affine transforms + /// Contains helper methods for working with affine and non-affine transforms /// internal class TransformHelpers { @@ -29,7 +29,32 @@ namespace SixLabors.ImageSharp var bl = Vector2.Transform(new Vector2(0, rectangle.Height), matrix); var br = Vector2.Transform(new Vector2(rectangle.Width, rectangle.Height), matrix); - // Find the minimum and maximum "corners" based on the ones above + return GetBoundingRectangle(tl, tr, bl, br); + } + + /// + /// Returns the bounding relative to the source for the given transformation matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix4x4 matrix) + { + // Calculate the position of the four corners in world space by applying + // The world matrix to the four corners in object space (0, 0, width, height) + var tl = Vector2.Transform(Vector2.Zero, matrix); + var tr = Vector2.Transform(new Vector2(rectangle.Width, 0), matrix); + var bl = Vector2.Transform(new Vector2(0, rectangle.Height), matrix); + var br = Vector2.Transform(new Vector2(rectangle.Width, rectangle.Height), matrix); + + return GetBoundingRectangle(tl, tr, bl, br); + } + + private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) + { + // Find the minimum and maximum "corners" based on the given vectors float minX = MathF.Min(tl.X, MathF.Min(tr.X, MathF.Min(bl.X, br.X))); float maxX = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X))); float minY = MathF.Min(tl.Y, MathF.Min(tr.Y, MathF.Min(bl.Y, br.Y)));