Browse Source

Refactor transforms to allow freeform + rectangle

af/merge-core
James Jackson-South 8 years ago
parent
commit
6d08237859
  1. 84
      src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
  2. 31
      src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs
  3. 52
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  4. 14
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  5. 18
      src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
  6. 16
      src/ImageSharp/Processing/Transforms/Transform.cs

84
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<TPixel>
{
private Rectangle targetRectangle;
private Matrix3x2 transformMatrix;
/// <summary>
/// Initializes a new instance of the <see cref="AffineProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
protected AffineProcessor(IResampler sampler)
protected AffineProcessor(Matrix3x2 matrix, IResampler sampler)
: this(matrix, sampler, Rectangle.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AffineProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
/// <param name="rectangle">The rectangle to constrain the transformed image to.</param>
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;
}
/// <summary>
/// Gets the sampler to perform interpolation of the transform operation.
/// Gets the matrix used to supply the affine transform
/// </summary>
public IResampler Sampler { get; }
public Matrix3x2 TransformMatrix { get; }
/// <summary>
/// Returns the processing matrix used for transforming the image.
/// Gets the sampler to perform interpolation of the transform operation.
/// </summary>
/// <returns>The <see cref="Matrix3x2"/></returns>
protected abstract Matrix3x2 GetTransformMatrix();
public IResampler Sampler { get; }
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination(Image<TPixel> 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<ImageFrame<TPixel>> 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
}
}
/// <inheritdoc/>
protected override void AfterImageApply(Image<TPixel> source, Image<TPixel> 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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source image.</param>
/// <param name="sourceRectangle">The source image bounds.</param>
/// <param name="destinationRectangle">The destination image bounds.</param>
/// <returns>
/// The <see cref="Matrix3x2"/>.
/// </returns>
protected Matrix3x2 GetCenteredMatrix(ImageFrame<TPixel> 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;
}
/// <summary>
@ -269,19 +310,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
}
/// <summary>
/// Creates a new target canvas to contain the results of the matrix transform.
/// </summary>
/// <param name="sourceRectangle">The source rectangle.</param>
private void ResizeCanvas(Rectangle sourceRectangle)
{
this.transformMatrix = this.GetTransformMatrix();
this.targetRectangle = Matrix3x2.Invert(this.transformMatrix, out Matrix3x2 sizeMatrix)
? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix)
: sourceRectangle;
}
/// <summary>
/// Calculates the sampling radius for the current sampler
/// </summary>

31
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<TPixel> : AffineProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="CenteredAffineProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
protected CenteredAffineProcessor(Matrix3x2 matrix, IResampler sampler)
: base(matrix, sampler)
{
}
/// <inheritdoc/>
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;
}
}
}

52
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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class RotateProcessor<TPixel> : AffineProcessor<TPixel>
internal class RotateProcessor<TPixel> : CenteredAffineProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class.
/// </summary>
/// <param name="angle">The angle of rotation in degrees.</param>
public RotateProcessor(float angle)
: this(angle, KnownResamplers.NearestNeighbor)
/// <param name="degrees">The angle of rotation in degrees.</param>
public RotateProcessor(float degrees)
: this(degrees, KnownResamplers.NearestNeighbor)
{
}
@ -34,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="degrees">The angle of rotation in degrees.</param>
/// <param name="sampler">The sampler to perform the rotating operation.</param>
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
/// </summary>
public float Degrees { get; }
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, ImageFrame<TPixel> 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);
}
/// <summary>
/// Wraps a given angle in degrees so that it falls withing the 0-360 degree range
/// </summary>
/// <param name="degrees">The angle of rotation in degrees.</param>
/// <returns>The <see cref="float"/></returns>
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;
}
/// <summary>
@ -99,26 +102,29 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </returns>
private bool OptimizedApply(ImageFrame<TPixel> source, ImageFrame<TPixel> 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;

14
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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class SkewProcessor<TPixel> : AffineProcessor<TPixel>
internal class SkewProcessor<TPixel> : CenteredAffineProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
@ -31,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="degreesY">The angle in degrees to perform the skew along the y-axis.</param>
/// <param name="sampler">The sampler to perform the skew operation.</param>
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.
/// </summary>
public float DegreesY { get; }
/// <inheritdoc/>
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;
}
}
}

18
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
/// <param name="matrix">The transformation matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
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;
}
/// <summary>
/// Gets the transform matrix
/// Initializes a new instance of the <see cref="TransformProcessor{TPixel}"/> class.
/// </summary>
public Matrix3x2 TransformMatrix { get; }
/// <inheritdoc />
protected override Matrix3x2 GetTransformMatrix()
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
/// <param name="rectangle">The rectangle to constrain the transformed image to.</param>
public TransformProcessor(Matrix3x2 matrix, IResampler sampler, Rectangle rectangle)
: base(matrix, sampler, rectangle)
{
return this.TransformMatrix;
}
}
}

16
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
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix3x2 matrix, IResampler sampler)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new TransformProcessor<TPixel>(matrix, sampler));
=> Transform(source, matrix, sampler, Rectangle.Empty);
/// <summary>
/// Transforms an image by the given matrix using the specified sampling algorithm.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to skew.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <param name="rectangle">The rectangle to constrain the transformed image to.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix3x2 matrix, IResampler sampler, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new TransformProcessor<TPixel>(matrix, sampler, rectangle));
}
}
Loading…
Cancel
Save