Browse Source

Add internal non-affine transform methods

pull/386/head
James Jackson-South 8 years ago
parent
commit
a2f0d24893
  1. 10
      ImageSharp.sln.DotSettings
  2. 138
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  3. 10
      src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
  4. 43
      src/ImageSharp/Processing/Processors/Transforms/CenteredNonAffineTransformProcessor.cs
  5. 139
      src/ImageSharp/Processing/Processors/Transforms/InterpolatedTransformProcessorBase.cs
  6. 238
      src/ImageSharp/Processing/Processors/Transforms/NonAffineTransformProcessor.cs
  7. 2
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  8. 2
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  9. 47
      src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
  10. 46
      src/ImageSharp/Processing/Transforms/Transform.cs
  11. 29
      src/ImageSharp/Processing/Transforms/TransformHelpers.cs

10
ImageSharp.sln.DotSettings

@ -38,10 +38,15 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">NEXT_LINE_SHIFTED_2</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_EXPR_MEMBER_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INITIALIZER_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CONSTRUCTOR_INITIALIZER_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_EMBEDDED_STATEMENT_ON_SAME_LINE/@EntryValue">ALWAYS</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_TYPE_CONSTRAINTS_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">ON_SINGLE_LINE</s:String>
@ -370,8 +375,13 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>

138
src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs → 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.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class AffineProcessor<TPixel> : CloningImageProcessor<TPixel>
internal class AffineTransformProcessor<TPixel> : InterpolatedTransformProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private Rectangle targetRectangle;
/// <summary>
/// Initializes a new instance of the <see cref="AffineProcessor{TPixel}"/> class.
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
public AffineTransformProcessor(Matrix3x2 matrix)
: this(matrix, KnownResamplers.Bicubic)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
protected AffineProcessor(Matrix3x2 matrix, IResampler sampler)
public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler)
: this(matrix, sampler, Rectangle.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AffineProcessor{TPixel}"/> class.
/// Initializes a new instance of the <see cref="AffineTransformProcessor{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)
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
/// </summary>
public Matrix3x2 TransformMatrix { get; }
/// <summary>
/// Gets the sampler to perform interpolation of the transform operation.
/// </summary>
public IResampler Sampler { get; }
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination(Image<TPixel> 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
}
}
/// <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 for final processing based upon the target image bounds.
/// </summary>
@ -254,91 +234,5 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
return sourceRectangle;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="min">The minimum sampling offset</param>
/// <param name="max">The maximum sampling offset</param>
/// <param name="sourceMin">The minimum source bounds</param>
/// <param name="sourceMax">The maximum source bounds</param>
/// <param name="point">The transformed point dimension</param>
/// <param name="sampler">The sampler</param>
/// <param name="scale">The transformed image scale relative to the source</param>
/// <param name="weights">The collection of weights</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CalculateWeightsDown(int min, int max, int sourceMin, int sourceMax, float point, IResampler sampler, float scale, Span<float> 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;
}
}
}
/// <summary>
/// Calculated the weights for the given point.
/// </summary>
/// <param name="sourceMin">The minimum source bounds</param>
/// <param name="sourceMax">The maximum source bounds</param>
/// <param name="point">The transformed point dimension</param>
/// <param name="sampler">The sampler</param>
/// <param name="weights">The collection of weights</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CalculateWeightsScaleUp(int sourceMin, int sourceMax, float point, IResampler sampler, Span<float> 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;
}
}
/// <summary>
/// Calculates the sampling radius for the current sampler
/// </summary>
/// <param name="sourceSize">The source dimension size</param>
/// <param name="destinationSize">The destination dimension size</param>
/// <returns>The radius, and scaling factor</returns>
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);
}
}
}

10
src/ImageSharp/Processing/Processors/Transforms/CenteredAffineProcessor.cs → src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs

@ -7,15 +7,19 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
internal abstract class CenteredAffineProcessor<TPixel> : AffineProcessor<TPixel>
/// <summary>
/// A base class that provides methods to allow the automatic centering of affine transforms
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class CenteredAffineTransformProcessor<TPixel> : AffineTransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="CenteredAffineProcessor{TPixel}"/> class.
/// Initializes a new instance of the <see cref="CenteredAffineTransformProcessor{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)
protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler)
: base(matrix, sampler)
{
}

43
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
{
/// <summary>
/// A base class that provides methods to allow the automatic centering of non-affine transforms
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class CenteredNonAffineTransformProcessor<TPixel> : NonAffineTransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="CenteredNonAffineTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
protected CenteredNonAffineTransformProcessor(Matrix4x4 matrix, IResampler sampler)
: base(matrix, sampler)
{
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
protected override Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix)
{
return Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 sizeMatrix)
? TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, sizeMatrix)
: sourceRectangle;
}
}
}

139
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
{
/// <summary>
/// The base class for performing interpolated affine and non-affine transforms.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class InterpolatedTransformProcessorBase<TPixel> : CloningImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="InterpolatedTransformProcessorBase{TPixel}"/> class.
/// </summary>
/// <param name="sampler">The sampler to perform the transform operation.</param>
protected InterpolatedTransformProcessorBase(IResampler sampler)
{
this.Sampler = sampler;
}
/// <summary>
/// Gets the sampler to perform interpolation of the transform operation.
/// </summary>
public IResampler Sampler { get; }
/// <summary>
/// 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.
/// </summary>
/// <param name="min">The minimum sampling offset</param>
/// <param name="max">The maximum sampling offset</param>
/// <param name="sourceMin">The minimum source bounds</param>
/// <param name="sourceMax">The maximum source bounds</param>
/// <param name="point">The transformed point dimension</param>
/// <param name="sampler">The sampler</param>
/// <param name="scale">The transformed image scale relative to the source</param>
/// <param name="weights">The collection of weights</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static void CalculateWeightsDown(int min, int max, int sourceMin, int sourceMax, float point, IResampler sampler, float scale, Span<float> 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;
}
}
}
/// <summary>
/// Calculated the weights for the given point.
/// </summary>
/// <param name="sourceMin">The minimum source bounds</param>
/// <param name="sourceMax">The maximum source bounds</param>
/// <param name="point">The transformed point dimension</param>
/// <param name="sampler">The sampler</param>
/// <param name="weights">The collection of weights</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static void CalculateWeightsScaleUp(int sourceMin, int sourceMax, float point, IResampler sampler, Span<float> 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;
}
}
/// <summary>
/// Calculates the sampling radius for the current sampler
/// </summary>
/// <param name="sourceSize">The source dimension size</param>
/// <param name="destinationSize">The destination dimension size</param>
/// <returns>The radius, and scaling factor</returns>
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);
}
/// <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);
}
}
}
}

238
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
{
/// <summary>
/// Provides the base methods to perform non-affine transforms on an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class NonAffineTransformProcessor<TPixel> : InterpolatedTransformProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private Rectangle targetRectangle;
/// <summary>
/// Initializes a new instance of the <see cref="NonAffineTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
public NonAffineTransformProcessor(Matrix4x4 matrix)
: this(matrix, KnownResamplers.Bicubic)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NonAffineTransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
public NonAffineTransformProcessor(Matrix4x4 matrix, IResampler sampler)
: this(matrix, sampler, Rectangle.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NonAffineTransformProcessor{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>
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;
}
/// <summary>
/// Gets the matrix used to supply the non-affine transform
/// </summary>
public Matrix4x4 TransformMatrix { get; }
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination(Image<TPixel> 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<ImageFrame<TPixel>> frames =
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);
}
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, ImageFrame<TPixel> 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<TPixel> 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<float>(yLength, height))
using (var xBuffer = new Buffer2D<float>(xLength, height))
{
Parallel.For(
0,
height,
configuration.ParallelOptions,
y =>
{
Span<TPixel> destRow = destination.GetPixelRowSpan(y);
Span<float> ySpan = yBuffer.GetRowSpan(y);
Span<float> 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));
}
});
}
}
/// <summary>
/// Gets a transform matrix adjusted for final processing based upon the target image bounds.
/// </summary>
/// <param name="sourceRectangle">The source image bounds.</param>
/// <param name="destinationRectangle">The destination image bounds.</param>
/// <returns>
/// The <see cref="Matrix4x4"/>.
/// </returns>
protected virtual Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle)
{
return this.TransformMatrix;
}
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> relative to the source for the given transformation matrix.
/// </summary>
/// <param name="sourceRectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Rectangle"/></returns>
protected virtual Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix)
{
return sourceRectangle;
}
}
}

2
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs

@ -15,7 +15,7 @@ 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> : CenteredAffineProcessor<TPixel>
internal class RotateProcessor<TPixel> : CenteredAffineTransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>

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

@ -10,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> : CenteredAffineProcessor<TPixel>
internal class SkewProcessor<TPixel> : CenteredAffineTransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>

47
src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs

@ -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
{
/// <summary>
/// Provides methods that allow the tranformation of images using various algorithms.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class TransformProcessor<TPixel> : AffineProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="TransformProcessor{TPixel}"/> class.
/// </summary>
/// <param name="matrix">The transformation matrix</param>
public TransformProcessor(Matrix3x2 matrix)
: this(matrix, KnownResamplers.Bicubic)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TransformProcessor{TPixel}"/> class.
/// </summary>
/// <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(matrix, sampler)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TransformProcessor{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>
public TransformProcessor(Matrix3x2 matrix, IResampler sampler, Rectangle rectangle)
: base(matrix, sampler, rectangle)
{
}
}
}

46
src/ImageSharp/Processing/Transforms/Transform.cs

@ -18,18 +18,18 @@ namespace SixLabors.ImageSharp
/// Transforms an image by the given matrix.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to skew.</param>
/// <param name="source">The image to transform.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel>
=> Transform(source, matrix, KnownResamplers.NearestNeighbor);
=> Transform(source, matrix, KnownResamplers.NearestNeighbor);
/// <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="source">The image to transform.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
@ -41,13 +41,49 @@ namespace SixLabors.ImageSharp
/// 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="source">The image to transform.</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));
=> source.ApplyProcessor(new AffineTransformProcessor<TPixel>(matrix, sampler, rectangle));
/// <summary>
/// Transforms an image by the given matrix.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image to transform.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
internal static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel>
=> Transform(source, matrix, KnownResamplers.NearestNeighbor);
/// <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 transform.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
/// <returns>The <see cref="Image{TPixel}"/></returns>
internal static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix, IResampler sampler)
where TPixel : struct, IPixel<TPixel>
=> 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 transform.</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>
internal static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix, IResampler sampler, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new NonAffineTransformProcessor<TPixel>(matrix, sampler, rectangle));
}
}

29
src/ImageSharp/Processing/Transforms/TransformHelpers.cs

@ -8,7 +8,7 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Contains helper methods for working with affine transforms
/// Contains helper methods for working with affine and non-affine transforms
/// </summary>
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);
}
/// <summary>
/// Returns the bounding <see cref="Rectangle"/> relative to the source for the given transformation matrix.
/// </summary>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
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)));

Loading…
Cancel
Save