@ -3,7 +3,6 @@
using System ;
using System.Collections.Generic ;
using System.Diagnostics.CodeAnalysis ;
using System.Linq ;
using System.Numerics ;
using System.Threading.Tasks ;
@ -18,52 +17,40 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Provides the base methods to perform affine transforms on an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class AffineProcessor < TPixel > : ResamplingWeighted Processor< TPixel >
internal abstract class AffineProcessor < TPixel > : CloningImage Processor< TPixel >
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="sampler">The sampler to perform the resize operation.</param>
protected AffineProcessor ( IResampler sampler )
: base ( sampler , 1 , 1 , Rectangles . DefaultRectangle ) // Hack to prevent Guard throwing in base, we always set the canvas
{
this . Sampler = sampler ;
}
/// <summary>
/// Gets or sets a value indicating whether to expand the canvas to fit the transformed image .
/// Gets the sampler to perform interpolation of the transform operation .
/// </summary>
public bool Expand { get ; set ; } = true ;
public IResampler Sampler { get ; }
/// <summary>
/// Returns the processing matrix used for transforming the image.
/// </summary>
/// <param name="rectangle">The rectangle bounds</param>
/// <returns>The <see cref="Matrix3x2"/></returns>
protected abstract Matrix3x2 CreateProcessingMatrix ( Rectangle rectangle ) ;
/// <summary>
/// Creates a new target canvas to contain the results of the matrix transform.
/// </summary>
/// <param name="sourceRectangle">The source rectangle.</param>
protected virtual void CreateNewCanvas ( Rectangle sourceRectangle )
{
this . ResizeRectangle = Matrix3x2 . Invert ( this . CreateProcessingMatrix ( sourceRectangle ) , out Matrix3x2 sizeMatrix )
? ImageMaths . GetBoundingRectangle ( sourceRectangle , sizeMatrix )
: sourceRectangle ;
this . Width = this . ResizeRectangle . Width ;
this . Height = this . ResizeRectangle . Height ;
}
protected abstract Matrix3x2 GetTransformMatrix ( ) ;
/// <inheritdoc/>
protected override Image < TPixel > CreateDestination ( Image < TPixel > source , Rectangle sourceRectangle )
{
this . CreateNew Canvas( sourceRectangle ) ;
this . ResizeCanvas ( sourceRectangle ) ;
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable < ImageFrame < TPixel > > frames =
source . Frames . Select ( x = > new ImageFrame < TPixel > ( this . Resize Rectangle. Width , this . Resize Rectangle. Height , x . MetaData . Clone ( ) ) ) ;
source . Frames . Select ( x = > new ImageFrame < TPixel > ( this . targetRectangle . Width , this . targetRectangle . Height , x . MetaData . Clone ( ) ) ) ;
// Use the overload to prevent an extra frame being added
return new Image < TPixel > ( source . GetConfiguration ( ) , source . MetaData . Clone ( ) , frames ) ;
@ -72,9 +59,11 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <inheritdoc/>
protected override void OnApply ( ImageFrame < TPixel > source , ImageFrame < TPixel > destination , Rectangle sourceRectangle , Configuration configuration )
{
int height = this . Resize Rectangle. Height ;
int width = this . Resize Rectangle. Width ;
int height = this . target Rectangle. Height ;
int width = this . target Rectangle. Width ;
Rectangle sourceBounds = source . Bounds ( ) ;
// Since could potentially be resizing the canvas we need to recenter the matrix
Matrix3x2 matrix = this . GetCenteredMatrix ( source ) ;
if ( this . Sampler is NearestNeighborResampler )
@ -106,8 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
( float radius , float scale ) yRadiusScale = this . GetSamplingRadius ( source . Height , destination . Height ) ;
float xScale = xRadiusScale . scale ;
float yScale = yRadiusScale . scale ;
float xRadius = xRadiusScale . radius ;
float yRadius = yRadiusScale . radius ;
var radius = new Vector2 ( xRadiusScale . radius , yRadiusScale . radius ) ;
IResampler sampler = this . Sampler ;
var maxSource = new Vector4 ( maxSourceX , maxSourceY , maxSourceX , maxSourceY ) ;
@ -125,11 +113,14 @@ namespace SixLabors.ImageSharp.Processing.Processors
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 ;
var extents = new Vector4 (
MathF . Ceiling ( point . X + xRadius ) ,
MathF . Ceiling ( point . Y + yRadius ) ,
MathF . Floor ( point . X - xRadius ) ,
MathF . Floor ( point . Y - yRadius ) ) ;
MathF . Ceiling ( maxXY . X ) ,
MathF . Ceiling ( maxXY . Y ) ,
MathF . Floor ( minXY . X ) ,
MathF . Floor ( minXY . Y ) ) ;
extents = Vector4 . Clamp ( extents , Vector4 . Zero , maxSource ) ;
@ -172,9 +163,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </returns>
protected Matrix3x2 GetCenteredMatrix ( ImageFrame < TPixel > source )
{
var translationToTargetCenter = Matrix3x2 . CreateTranslation ( - this . Resize Rectangle. Width * . 5F , - this . Resize Rectangle. Height * . 5F ) ;
var translationToTargetCenter = Matrix3x2 . CreateTranslation ( - this . target Rectangle. Width * . 5F , - this . target Rectangle. Height * . 5F ) ;
var translateToSourceCenter = Matrix3x2 . CreateTranslation ( source . Width * . 5F , source . Height * . 5F ) ;
return ( translationToTargetCenter * this . CreateProcessingMatrix ( this . ResizeRectangle ) ) * translateToSourceCenter ;
return translationToTargetCenter * this . transformMatrix * translateToSourceCenter ;
}
/// <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>
@ -196,14 +199,4 @@ namespace SixLabors.ImageSharp.Processing.Processors
return ( MathF . Ceiling ( scale * this . Sampler . Radius ) , scale ) ;
}
}
/// <summary>
/// Contains a static rectangle used for comparison when creating a new canvas.
/// We do this so we can inherit from the resampling weights class and pass the guard in the constructor and also avoid creating a new rectangle each time.
/// </summary>
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = "I'm using this only here to prevent duplication in generic types.")]
internal static class Rectangles
{
public static Rectangle DefaultRectangle { get ; } = new Rectangle ( 0 , 0 , 1 , 1 ) ;
}
}