diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
index f7ba651778..767c6feed1 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/AffineProcessor.cs
@@ -65,7 +65,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 recenter the matrix
+ // Since could potentially be resizing the canvas we need to re-center the matrix
Matrix3x2 matrix = this.GetCenteredMatrix(source);
if (this.Sampler is NearestNeighborResampler)
@@ -146,10 +146,10 @@ namespace SixLabors.ImageSharp.Processing.Processors
continue;
}
- // TODO: Find a way to speed this up if we can we precalculated weights!!!
// It appears these have to be calculated on-the-fly.
- // Check with Anton to figure out why indexing from the precalculated weights was wrong.
- // It might not be possible to do so with the resizer weights but perhaps we can fashion something similar for here.
+ // Precalulating transformed weights would require prior knowledge of every transformed pixel location
+ // since they can be at sub-pixel positions.
+ // I've optimized where I can but am always open to suggestions.
//
// Create and normalize the y-weights
if (yScale > 1)
@@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
CalculateWeightsScaleUp(minX, maxX, point.X, sampler, xBuffer);
}
- // Now multiply the normalized results against the offsets
+ // Now multiply the results against the offsets
Vector4 sum = Vector4.Zero;
for (int yy = 0, j = minY; j <= maxY; j++, yy++)
{
@@ -205,24 +205,36 @@ namespace SixLabors.ImageSharp.Processing.Processors
return translationToTargetCenter * this.transformMatrix * translateToSourceCenter;
}
+ ///
+ /// 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.
+ ///
+ /// The minimum sampling offset
+ /// The maximum sampling offset
+ /// The minimum source bounds
+ /// The maximum source bounds
+ /// The transformed point dimension
+ /// The sampler
+ /// The transformed image scale relative to the source
+ /// The collection of weights
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CalculateWeightsDown(int top, int bottom, int min, int max, float point, IResampler sampler, float scale, Buffer weights)
+ private static void CalculateWeightsDown(int min, int max, int sourceMin, int sourceMax, float point, IResampler sampler, float scale, Buffer 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 = top; i <= bottom; i++, x++)
+ for (int x = 0, i = min; i <= max; i++, x++)
{
int index = i;
- if (index < min)
+ if (index < sourceMin)
{
- index = min;
+ index = sourceMin;
}
- if (index > max)
+ if (index > sourceMax)
{
- index = max;
+ index = sourceMax;
}
float weight = sampler.GetValue((index - point) / scale);
@@ -240,11 +252,20 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
}
+ ///
+ /// 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.
+ ///
+ /// The minimum source bounds
+ /// The maximum source bounds
+ /// The transformed point dimension
+ /// The sampler
+ /// The collection of weights
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CalculateWeightsScaleUp(int min, int max, float point, IResampler sampler, Buffer weights)
+ private static void CalculateWeightsScaleUp(int sourceMin, int sourceMax, float point, IResampler sampler, Buffer weights)
{
ref float weightsBaseRef = ref weights[0];
- for (int x = 0, i = min; i <= max; i++, x++)
+ for (int x = 0, i = sourceMin; i <= sourceMax; i++, x++)
{
float weight = sampler.GetValue(i - point);
Unsafe.Add(ref weightsBaseRef, x) = weight;
@@ -259,10 +280,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
this.transformMatrix = this.GetTransformMatrix();
- // this.targetRectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, this.transformMatrix);
this.targetRectangle = Matrix3x2.Invert(this.transformMatrix, out Matrix3x2 sizeMatrix)
- ? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix)
- : sourceRectangle;
+ ? ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix)
+ : sourceRectangle;
}
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
deleted file mode 100644
index 1169d2eadc..0000000000
--- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
+++ /dev/null
@@ -1,199 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using System;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Processing.Processors
-{
- ///
- /// Conains the definition of and .
- ///
- internal abstract partial class ResamplingWeightedProcessor
- {
- ///
- /// Points to a collection of of weights allocated in .
- ///
- internal struct WeightsWindow
- {
- ///
- /// The local left index position
- ///
- public int Left;
-
- ///
- /// The length of the weights window
- ///
- public int Length;
-
- ///
- /// The index in the destination buffer
- ///
- private readonly int flatStartIndex;
-
- ///
- /// The buffer containing the weights values.
- ///
- private readonly Buffer buffer;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The destination index in the buffer
- /// The local left index
- /// The span
- /// The length of the window
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal WeightsWindow(int index, int left, Buffer2D buffer, int length)
- {
- this.flatStartIndex = (index * buffer.Width) + left;
- this.Left = left;
- this.buffer = buffer;
- this.Length = length;
- }
-
- ///
- /// Gets a reference to the first item of the window.
- ///
- /// The reference to the first item of the window
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ref float GetStartReference()
- {
- return ref this.buffer[this.flatStartIndex];
- }
-
- ///
- /// Gets the span representing the portion of the that this window covers
- ///
- /// The
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Span GetWindowSpan() => this.buffer.Slice(this.flatStartIndex, this.Length);
-
- ///
- /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
- ///
- /// The input span of vectors
- /// The source row position.
- /// The weighted sum
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector4 ComputeWeightedRowSum(Span rowSpan, int sourceX)
- {
- ref float horizontalValues = ref this.GetStartReference();
- int left = this.Left;
- ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX);
-
- // Destination color components
- Vector4 result = Vector4.Zero;
-
- for (int i = 0; i < this.Length; i++)
- {
- float weight = Unsafe.Add(ref horizontalValues, i);
- Vector4 v = Unsafe.Add(ref vecPtr, i);
- result += v * weight;
- }
-
- return result;
- }
-
- ///
- /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
- /// Applies to all input vectors.
- ///
- /// The input span of vectors
- /// The source row position.
- /// The weighted sum
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector4 ComputeExpandedWeightedRowSum(Span rowSpan, int sourceX)
- {
- ref float horizontalValues = ref this.GetStartReference();
- int left = this.Left;
- ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX);
-
- // Destination color components
- Vector4 result = Vector4.Zero;
-
- for (int i = 0; i < this.Length; i++)
- {
- float weight = Unsafe.Add(ref horizontalValues, i);
- Vector4 v = Unsafe.Add(ref vecPtr, i);
- result += v.Expand() * weight;
- }
-
- return result;
- }
-
- ///
- /// Computes the sum of vectors in 'firstPassPixels' at a row pointed by 'x',
- /// weighted by weight values, pointed by this instance.
- ///
- /// The buffer of input vectors in row first order
- /// The row position
- /// The source column position.
- /// The weighted sum
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Vector4 ComputeWeightedColumnSum(Buffer2D firstPassPixels, int x, int sourceY)
- {
- ref float verticalValues = ref this.GetStartReference();
- int left = this.Left;
-
- // Destination color components
- Vector4 result = Vector4.Zero;
-
- for (int i = 0; i < this.Length; i++)
- {
- float yw = Unsafe.Add(ref verticalValues, i);
- int index = left + i + sourceY;
- result += firstPassPixels[x, index] * yw;
- }
-
- return result;
- }
- }
-
- ///
- /// Holds the values in an optimized contigous memory region.
- ///
- internal class WeightsBuffer : IDisposable
- {
- private readonly Buffer2D dataBuffer;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The size of the source window
- /// The size of the destination window
- public WeightsBuffer(int sourceSize, int destinationSize)
- {
- this.dataBuffer = Buffer2D.CreateClean(sourceSize, destinationSize);
- this.Weights = new WeightsWindow[destinationSize];
- }
-
- ///
- /// Gets the calculated values.
- ///
- public WeightsWindow[] Weights { get; }
-
- ///
- /// Disposes instance releasing it's backing buffer.
- ///
- public void Dispose()
- {
- this.dataBuffer.Dispose();
- }
-
- ///
- /// Slices a weights value at the given positions.
- ///
- /// The index in destination buffer
- /// The local left index value
- /// The local right index value
- /// The weights
- public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx)
- {
- return new WeightsWindow(destIdx, leftIdx, this.dataBuffer, rightIdx - leftIdx + 1);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
index 781f3a01c7..cb65559daa 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
@@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// Adapted from
///
/// The pixel format.
- internal abstract partial class ResamplingWeightedProcessor : CloningImageProcessor
+ internal abstract class ResamplingWeightedProcessor : CloningImageProcessor
where TPixel : struct, IPixel
{
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
index be49ec321b..e3fd36ce69 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
@@ -47,7 +47,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
///
protected override Matrix3x2 GetTransformMatrix()
{
- return Matrix3x2Extensions.CreateRotationDegrees(-this.Degrees, PointF.Empty);
+ Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(this.Degrees, PointF.Empty);
+ Matrix3x2.Invert(matrix, out matrix);
+ return matrix;
}
///
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
index 8da8b1e57b..eb9f068db0 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
@@ -50,7 +50,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
///
protected override Matrix3x2 GetTransformMatrix()
{
- return Matrix3x2Extensions.CreateSkewDegrees(-this.DegreesX, -this.DegreesY, PointF.Empty);
+ 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 62bdc4a1eb..45e9d4dd5e 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs
@@ -30,6 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
public TransformProcessor(Matrix3x2 matrix, IResampler sampler)
: base(sampler)
{
+ Matrix3x2.Invert(matrix, out matrix);
this.TransformMatrix = matrix;
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs
new file mode 100644
index 0000000000..0e91087f90
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Processing.Processors
+{
+ ///
+ /// Holds the values in an optimized contigous memory region.
+ ///
+ internal class WeightsBuffer : IDisposable
+ {
+ private readonly Buffer2D dataBuffer;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The size of the source window
+ /// The size of the destination window
+ public WeightsBuffer(int sourceSize, int destinationSize)
+ {
+ this.dataBuffer = Buffer2D.CreateClean(sourceSize, destinationSize);
+ this.Weights = new WeightsWindow[destinationSize];
+ }
+
+ ///
+ /// Gets the calculated values.
+ ///
+ public WeightsWindow[] Weights { get; }
+
+ ///
+ /// Disposes instance releasing it's backing buffer.
+ ///
+ public void Dispose()
+ {
+ this.dataBuffer.Dispose();
+ }
+
+ ///
+ /// Slices a weights value at the given positions.
+ ///
+ /// The index in destination buffer
+ /// The local left index value
+ /// The local right index value
+ /// The weights
+ public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx)
+ {
+ return new WeightsWindow(destIdx, leftIdx, this.dataBuffer, rightIdx - leftIdx + 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs
new file mode 100644
index 0000000000..d23ee5a060
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs
@@ -0,0 +1,150 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Processing.Processors
+{
+ ///
+ /// Points to a collection of of weights allocated in .
+ ///
+ internal struct WeightsWindow
+ {
+ ///
+ /// The local left index position
+ ///
+ public int Left;
+
+ ///
+ /// The length of the weights window
+ ///
+ public int Length;
+
+ ///
+ /// The index in the destination buffer
+ ///
+ private readonly int flatStartIndex;
+
+ ///
+ /// The buffer containing the weights values.
+ ///
+ private readonly Buffer buffer;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The destination index in the buffer
+ /// The local left index
+ /// The span
+ /// The length of the window
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal WeightsWindow(int index, int left, Buffer2D buffer, int length)
+ {
+ this.flatStartIndex = (index * buffer.Width) + left;
+ this.Left = left;
+ this.buffer = buffer;
+ this.Length = length;
+ }
+
+ ///
+ /// Gets a reference to the first item of the window.
+ ///
+ /// The reference to the first item of the window
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref float GetStartReference()
+ {
+ return ref this.buffer[this.flatStartIndex];
+ }
+
+ ///
+ /// Gets the span representing the portion of the that this window covers
+ ///
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span GetWindowSpan() => this.buffer.Slice(this.flatStartIndex, this.Length);
+
+ ///
+ /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
+ ///
+ /// The input span of vectors
+ /// The source row position.
+ /// The weighted sum
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 ComputeWeightedRowSum(Span rowSpan, int sourceX)
+ {
+ ref float horizontalValues = ref this.GetStartReference();
+ int left = this.Left;
+ ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX);
+
+ // Destination color components
+ Vector4 result = Vector4.Zero;
+
+ for (int i = 0; i < this.Length; i++)
+ {
+ float weight = Unsafe.Add(ref horizontalValues, i);
+ Vector4 v = Unsafe.Add(ref vecPtr, i);
+ result += v * weight;
+ }
+
+ return result;
+ }
+
+ ///
+ /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance.
+ /// Applies to all input vectors.
+ ///
+ /// The input span of vectors
+ /// The source row position.
+ /// The weighted sum
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 ComputeExpandedWeightedRowSum(Span rowSpan, int sourceX)
+ {
+ ref float horizontalValues = ref this.GetStartReference();
+ int left = this.Left;
+ ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX);
+
+ // Destination color components
+ Vector4 result = Vector4.Zero;
+
+ for (int i = 0; i < this.Length; i++)
+ {
+ float weight = Unsafe.Add(ref horizontalValues, i);
+ Vector4 v = Unsafe.Add(ref vecPtr, i);
+ result += v.Expand() * weight;
+ }
+
+ return result;
+ }
+
+ ///
+ /// Computes the sum of vectors in 'firstPassPixels' at a row pointed by 'x',
+ /// weighted by weight values, pointed by this instance.
+ ///
+ /// The buffer of input vectors in row first order
+ /// The row position
+ /// The source column position.
+ /// The weighted sum
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Vector4 ComputeWeightedColumnSum(Buffer2D firstPassPixels, int x, int sourceY)
+ {
+ ref float verticalValues = ref this.GetStartReference();
+ int left = this.Left;
+
+ // Destination color components
+ Vector4 result = Vector4.Zero;
+
+ for (int i = 0; i < this.Length; i++)
+ {
+ float yw = Unsafe.Add(ref verticalValues, i);
+ int index = left + i + sourceY;
+ result += firstPassPixels[x, index] * yw;
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
index 6dd6369802..98dbbadaba 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
@@ -40,11 +40,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{
var proc = new ResizeProcessor(KnownResamplers.Bicubic, 200, 200);
- ResamplingWeightedProcessor.WeightsBuffer weights = proc.PrecomputeWeights(200, 500);
+ WeightsBuffer weights = proc.PrecomputeWeights(200, 500);
var bld = new StringBuilder();
- foreach (ResamplingWeightedProcessor.WeightsWindow window in weights.Weights)
+ foreach (WeightsWindow window in weights.Weights)
{
Span span = window.GetWindowSpan();
for (int i = 0; i < window.Length; i++)
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformTests.cs
index 30cb626155..4562d7de73 100644
--- a/tests/ImageSharp.Tests/Processing/Transforms/TransformTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformTests.cs
@@ -17,8 +17,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public static readonly TheoryData TransformValues
= new TheoryData
{
- { 20, 10, 50 },
- { -20, -10, 50 }
+ { 20, 10, 45 },
+ { -20, -10, 45 }
};
public static readonly List ResamplerNames
@@ -52,13 +52,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
IResampler sampler = GetResampler(resamplerName);
using (Image image = provider.GetImage())
{
- Matrix3x2 rotate = Matrix3x2Extensions.CreateRotationDegrees(-z);
-
- // TODO, how does scale work? 2 means half just now,
+ Matrix3x2 rotate = Matrix3x2Extensions.CreateRotationDegrees(z);
Matrix3x2 scale = Matrix3x2Extensions.CreateScale(new SizeF(2F, 2F));
-
- image.Mutate(i => i.Transform(scale * rotate, sampler));
+ image.Mutate(i => i.Transform(rotate * scale, sampler));
image.DebugSave(provider, string.Join("_", x, y, resamplerName));
}
}