Browse Source

Refactor transforms to allow freeform + rectangle

af/merge-core
James Jackson-South 9 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.Advanced;
using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -23,32 +24,53 @@ namespace SixLabors.ImageSharp.Processing.Processors
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private Rectangle targetRectangle; private Rectangle targetRectangle;
private Matrix3x2 transformMatrix;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AffineProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="AffineProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</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.Sampler = sampler;
this.targetRectangle = rectangle;
} }
/// <summary> /// <summary>
/// Gets the sampler to perform interpolation of the transform operation. /// Gets the matrix used to supply the affine transform
/// </summary> /// </summary>
public IResampler Sampler { get; } public Matrix3x2 TransformMatrix { get; }
/// <summary> /// <summary>
/// Returns the processing matrix used for transforming the image. /// Gets the sampler to perform interpolation of the transform operation.
/// </summary> /// </summary>
/// <returns>The <see cref="Matrix3x2"/></returns> public IResampler Sampler { get; }
protected abstract Matrix3x2 GetTransformMatrix();
/// <inheritdoc/> /// <inheritdoc/>
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) 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 // We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = IEnumerable<ImageFrame<TPixel>> frames =
@ -66,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
Rectangle sourceBounds = source.Bounds(); 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 need to re-center the matrix
Matrix3x2 matrix = this.GetCenteredMatrix(source); Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, this.targetRectangle);
if (this.Sampler is NearestNeighborResampler) 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> /// <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> /// </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> /// <returns>
/// The <see cref="Matrix3x2"/>. /// The <see cref="Matrix3x2"/>.
/// </returns> /// </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); return this.TransformMatrix;
var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F);
return translationToTargetCenter * this.transformMatrix * translateToSourceCenter;
} }
/// <summary> /// <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> /// <summary>
/// Calculates the sampling radius for the current sampler /// Calculates the sampling radius for the current sampler
/// </summary> /// </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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Helpers;
@ -16,15 +15,15 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Provides methods that allow the rotating of images. /// Provides methods that allow the rotating of images.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal class RotateProcessor<TPixel> : AffineProcessor<TPixel> internal class RotateProcessor<TPixel> : CenteredAffineProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="RotateProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="angle">The angle of rotation in degrees.</param> /// <param name="degrees">The angle of rotation in degrees.</param>
public RotateProcessor(float angle) public RotateProcessor(float degrees)
: this(angle, KnownResamplers.NearestNeighbor) : 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="degrees">The angle of rotation in degrees.</param>
/// <param name="sampler">The sampler to perform the rotating operation.</param> /// <param name="sampler">The sampler to perform the rotating operation.</param>
public RotateProcessor(float degrees, IResampler sampler) public RotateProcessor(float degrees, IResampler sampler)
: base(sampler) : base(Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty), sampler)
{ {
this.Degrees = degrees; this.Degrees = degrees;
} }
@ -44,15 +43,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary> /// </summary>
public float Degrees { get; } 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/> /// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration) protected override void OnApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{ {
@ -73,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
return; return;
} }
if (MathF.Abs(this.Degrees) < Constants.Epsilon) if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon)
{ {
// No need to do anything so return. // No need to do anything so return.
return; return;
@ -81,11 +71,24 @@ namespace SixLabors.ImageSharp.Processing.Processors
profile.RemoveValue(ExifTag.Orientation); 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); degrees += 360;
profile.SetValue(ExifTag.PixelYDimension, source.Height);
} }
return degrees;
} }
/// <summary> /// <summary>
@ -99,26 +102,29 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </returns> /// </returns>
private bool OptimizedApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Configuration configuration) 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 // The destination will be blank here so copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return true; return true;
} }
if (MathF.Abs(this.Degrees - 90) < Constants.Epsilon) if (MathF.Abs(degrees - 90) < Constants.Epsilon)
{ {
this.Rotate90(source, destination, configuration); this.Rotate90(source, destination, configuration);
return true; return true;
} }
if (MathF.Abs(this.Degrees - 180) < Constants.Epsilon) if (MathF.Abs(degrees - 180) < Constants.Epsilon)
{ {
this.Rotate180(source, destination, configuration); this.Rotate180(source, destination, configuration);
return true; return true;
} }
if (MathF.Abs(this.Degrees - 270) < Constants.Epsilon) if (MathF.Abs(degrees - 270) < Constants.Epsilon)
{ {
this.Rotate270(source, destination, configuration); this.Rotate270(source, destination, configuration);
return true; return true;

14
src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -11,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Provides methods that allow the skewing of images. /// Provides methods that allow the skewing of images.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal class SkewProcessor<TPixel> : AffineProcessor<TPixel> internal class SkewProcessor<TPixel> : CenteredAffineProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <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="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> /// <param name="sampler">The sampler to perform the skew operation.</param>
public SkewProcessor(float degreesX, float degreesY, IResampler sampler) public SkewProcessor(float degreesX, float degreesY, IResampler sampler)
: base(sampler) : base(Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty), sampler)
{ {
this.DegreesX = degreesX; this.DegreesX = degreesX;
this.DegreesY = degreesY; this.DegreesY = degreesY;
@ -46,14 +45,5 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Gets the angle of rotation along the y-axis in degrees. /// Gets the angle of rotation along the y-axis in degrees.
/// </summary> /// </summary>
public float DegreesY { get; } 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 System.Numerics;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors namespace SixLabors.ImageSharp.Processing.Processors
{ {
@ -28,22 +29,19 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="matrix">The transformation matrix</param> /// <param name="matrix">The transformation matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param> /// <param name="sampler">The sampler to perform the transform operation.</param>
public TransformProcessor(Matrix3x2 matrix, IResampler sampler) 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> /// <summary>
/// Gets the transform matrix /// Initializes a new instance of the <see cref="TransformProcessor{TPixel}"/> class.
/// </summary> /// </summary>
public Matrix3x2 TransformMatrix { get; } /// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
/// <inheritdoc /> /// <param name="rectangle">The rectangle to constrain the transformed image to.</param>
protected override Matrix3x2 GetTransformMatrix() 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.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
@ -34,6 +35,19 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns> /// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix3x2 matrix, IResampler sampler) public static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix3x2 matrix, IResampler sampler)
where TPixel : struct, IPixel<TPixel> 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