From dcfa0a02c7707fc3f3da8829e5d2299ac0d1daf4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 13 Dec 2017 00:06:10 +1100 Subject: [PATCH] Refactor transforms to allow freeform + rectangle --- .../Processors/Transforms/AffineProcessor.cs | 84 ++++++++++++------- .../Transforms/CenteredAffineProcessor.cs | 31 +++++++ .../Processors/Transforms/RotateProcessor.cs | 52 +++++++----- .../Processors/Transforms/SkewProcessor.cs | 14 +--- .../Transforms/TransformProcessor.cs | 18 ++-- .../Processing/Transforms/Transform.cs | 16 +++- 6 files changed, 141 insertions(+), 74 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs index c9a4b2aeaf..b331201cc9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs @@ -10,6 +10,7 @@ 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; @@ -23,32 +24,53 @@ namespace SixLabors.ImageSharp.Processing.Processors where TPixel : struct, IPixel { private Rectangle targetRectangle; - private Matrix3x2 transformMatrix; /// /// Initializes a new instance of the class. /// + /// The transform matrix /// The sampler to perform the transform operation. - protected AffineProcessor(IResampler sampler) + protected AffineProcessor(Matrix3x2 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. + protected AffineProcessor(Matrix3x2 matrix, IResampler sampler, Rectangle rectangle) + { + // 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; } /// - /// Gets the sampler to perform interpolation of the transform operation. + /// Gets the matrix used to supply the affine transform /// - public IResampler Sampler { get; } + public Matrix3x2 TransformMatrix { get; } /// - /// Returns the processing matrix used for transforming the image. + /// Gets the sampler to perform interpolation of the transform operation. /// - /// The - protected abstract Matrix3x2 GetTransformMatrix(); + public IResampler Sampler { get; } /// protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { - this.ResizeCanvas(sourceRectangle); + if (this.targetRectangle == Rectangle.Empty) + { + this.targetRectangle = Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 sizeMatrix) + ? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix) + : sourceRectangle; + } // We will always be creating the clone even for mutate because we may need to resize the canvas IEnumerable> frames = @@ -66,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing.Processors Rectangle sourceBounds = source.Bounds(); // Since could potentially be resizing the canvas we need to re-center the matrix - Matrix3x2 matrix = this.GetCenteredMatrix(source); + Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, this.targetRectangle); if (this.Sampler is NearestNeighborResampler) { @@ -188,18 +210,37 @@ 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 to center upon the target image bounds. + /// Gets a transform matrix adjusted for final processing based upon the target image bounds. /// - /// The source image. + /// The source image bounds. + /// The destination image bounds. /// /// The . /// - protected Matrix3x2 GetCenteredMatrix(ImageFrame source) + protected virtual Matrix3x2 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) { - var translationToTargetCenter = Matrix3x2.CreateTranslation(-this.targetRectangle.Width * .5F, -this.targetRectangle.Height * .5F); - var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); - return translationToTargetCenter * this.transformMatrix * translateToSourceCenter; + return this.TransformMatrix; } /// @@ -269,19 +310,6 @@ namespace SixLabors.ImageSharp.Processing.Processors } } - /// - /// Creates a new target canvas to contain the results of the matrix transform. - /// - /// The source rectangle. - private void ResizeCanvas(Rectangle sourceRectangle) - { - this.transformMatrix = this.GetTransformMatrix(); - - this.targetRectangle = Matrix3x2.Invert(this.transformMatrix, out Matrix3x2 sizeMatrix) - ? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix) - : sourceRectangle; - } - /// /// Calculates the sampling radius for the current sampler /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs new file mode 100644 index 0000000000..5f03f94e27 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs @@ -0,0 +1,31 @@ +// 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 +{ + internal abstract class CenteredAffineProcessor : AffineProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The transform matrix + /// The sampler to perform the transform operation. + protected CenteredAffineProcessor(Matrix3x2 matrix, IResampler sampler) + : base(matrix, sampler) + { + } + + /// + protected override Matrix3x2 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) + { + var translationToTargetCenter = Matrix3x2.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F); + var translateToSourceCenter = Matrix3x2.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F); + return translationToTargetCenter * this.TransformMatrix * translateToSourceCenter; + } + } +} \ 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 b59918cea4..7af1c68f12 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Helpers; @@ -16,15 +15,15 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Provides methods that allow the rotating of images. /// /// The pixel format. - internal class RotateProcessor : AffineProcessor + internal class RotateProcessor : CenteredAffineProcessor where TPixel : struct, IPixel { /// /// Initializes a new instance of the class. /// - /// The angle of rotation in degrees. - public RotateProcessor(float angle) - : this(angle, KnownResamplers.NearestNeighbor) + /// The angle of rotation in degrees. + public RotateProcessor(float degrees) + : this(degrees, KnownResamplers.NearestNeighbor) { } @@ -34,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The angle of rotation in degrees. /// The sampler to perform the rotating operation. public RotateProcessor(float degrees, IResampler sampler) - : base(sampler) + : base(Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty), sampler) { this.Degrees = degrees; } @@ -44,15 +43,6 @@ namespace SixLabors.ImageSharp.Processing.Processors /// public float Degrees { get; } - /// - protected override Matrix3x2 GetTransformMatrix() - { - // Tansforms are inverted else the output is the opposite of the expected. - Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(this.Degrees, PointF.Empty); - Matrix3x2.Invert(matrix, out matrix); - return matrix; - } - /// protected override void OnApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) { @@ -73,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors return; } - if (MathF.Abs(this.Degrees) < Constants.Epsilon) + if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon) { // No need to do anything so return. return; @@ -81,11 +71,24 @@ namespace SixLabors.ImageSharp.Processing.Processors profile.RemoveValue(ExifTag.Orientation); - if (profile.GetValue(ExifTag.PixelXDimension) != null) + base.AfterImageApply(source, destination, sourceRectangle); + } + + /// + /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range + /// + /// The angle of rotation in degrees. + /// The + private static float WrapDegrees(float degrees) + { + degrees = degrees % 360; + + if (degrees < 0) { - profile.SetValue(ExifTag.PixelXDimension, source.Width); - profile.SetValue(ExifTag.PixelYDimension, source.Height); + degrees += 360; } + + return degrees; } /// @@ -99,26 +102,29 @@ namespace SixLabors.ImageSharp.Processing.Processors /// private bool OptimizedApply(ImageFrame source, ImageFrame destination, Configuration configuration) { - if (MathF.Abs(this.Degrees) < Constants.Epsilon) + // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. + float degrees = WrapDegrees(this.Degrees); + + if (MathF.Abs(degrees) < Constants.Epsilon) { // The destination will be blank here so copy all the pixel data over source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); return true; } - if (MathF.Abs(this.Degrees - 90) < Constants.Epsilon) + if (MathF.Abs(degrees - 90) < Constants.Epsilon) { this.Rotate90(source, destination, configuration); return true; } - if (MathF.Abs(this.Degrees - 180) < Constants.Epsilon) + if (MathF.Abs(degrees - 180) < Constants.Epsilon) { this.Rotate180(source, destination, configuration); return true; } - if (MathF.Abs(this.Degrees - 270) < Constants.Epsilon) + if (MathF.Abs(degrees - 270) < Constants.Epsilon) { this.Rotate270(source, destination, configuration); return true; diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index 321a6abe16..07f082838c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.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; using SixLabors.Primitives; @@ -11,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Provides methods that allow the skewing of images. /// /// The pixel format. - internal class SkewProcessor : AffineProcessor + internal class SkewProcessor : CenteredAffineProcessor where TPixel : struct, IPixel { /// @@ -31,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The angle in degrees to perform the skew along the y-axis. /// The sampler to perform the skew operation. public SkewProcessor(float degreesX, float degreesY, IResampler sampler) - : base(sampler) + : base(Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty), sampler) { this.DegreesX = degreesX; this.DegreesY = degreesY; @@ -46,14 +45,5 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Gets the angle of rotation along the y-axis in degrees. /// public float DegreesY { get; } - - /// - protected override Matrix3x2 GetTransformMatrix() - { - // Tansforms are inverted else the output is the opposite of the expected. - Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(this.DegreesX, this.DegreesY, PointF.Empty); - Matrix3x2.Invert(matrix, out matrix); - return matrix; - } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs index e2dbed7655..577691cbb5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs @@ -3,6 +3,7 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { @@ -28,22 +29,19 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The transformation matrix /// The sampler to perform the transform operation. public TransformProcessor(Matrix3x2 matrix, IResampler sampler) - : base(sampler) + : base(matrix, sampler) { - // Tansforms are inverted else the output is the opposite of the expected. - Matrix3x2.Invert(matrix, out matrix); - this.TransformMatrix = matrix; } /// - /// Gets the transform matrix + /// Initializes a new instance of the class. /// - public Matrix3x2 TransformMatrix { get; } - - /// - protected override Matrix3x2 GetTransformMatrix() + /// 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) { - return this.TransformMatrix; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Transform.cs b/src/ImageSharp/Processing/Transforms/Transform.cs index f3478e32d5..74f91fa701 100644 --- a/src/ImageSharp/Processing/Transforms/Transform.cs +++ b/src/ImageSharp/Processing/Transforms/Transform.cs @@ -5,6 +5,7 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; namespace SixLabors.ImageSharp { @@ -34,6 +35,19 @@ namespace SixLabors.ImageSharp /// The public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix, IResampler sampler) where TPixel : struct, IPixel - => source.ApplyProcessor(new TransformProcessor(matrix, sampler)); + => Transform(source, matrix, sampler, Rectangle.Empty); + + /// + /// Transforms an image by the given matrix using the specified sampling algorithm. + /// + /// The pixel format. + /// The image to skew. + /// 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)); } } \ No newline at end of file