Skip to content

Commit 00b2152

Browse files
Merge branch 'main' into js/issue-2801
2 parents 4b3afc1 + f61cbe7 commit 00b2152

12 files changed

+388
-84
lines changed

src/ImageSharp/Processing/AffineTransformBuilder.cs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing;
1111
/// </summary>
1212
public class AffineTransformBuilder
1313
{
14-
private readonly List<Func<Size, Matrix3x2>> transformMatrixFactories = new();
14+
private readonly List<Func<Size, Matrix3x2>> transformMatrixFactories = [];
1515

1616
/// <summary>
1717
/// Initializes a new instance of the <see cref="AffineTransformBuilder"/> class.
@@ -301,7 +301,8 @@ public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix)
301301
/// </summary>
302302
/// <param name="sourceSize">The source image size.</param>
303303
/// <returns>The <see cref="Matrix3x2"/>.</returns>
304-
public Matrix3x2 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize));
304+
public Matrix3x2 BuildMatrix(Size sourceSize)
305+
=> this.BuildMatrix(new Rectangle(Point.Empty, sourceSize));
305306

306307
/// <summary>
307308
/// Returns the combined transform matrix for a given source rectangle.
@@ -345,18 +346,8 @@ public Matrix3x2 BuildMatrix(Rectangle sourceRectangle)
345346
/// <returns>The <see cref="Size"/>.</returns>
346347
public Size GetTransformedSize(Rectangle sourceRectangle)
347348
{
348-
Size size = sourceRectangle.Size;
349-
350-
// Translate the origin matrix to cater for source rectangle offsets.
351-
Matrix3x2 matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location);
352-
353-
foreach (Func<Size, Matrix3x2> factory in this.transformMatrixFactories)
354-
{
355-
matrix *= factory(size);
356-
CheckDegenerate(matrix);
357-
}
358-
359-
return TransformUtils.GetTransformedSize(matrix, size, this.TransformSpace);
349+
Matrix3x2 matrix = this.BuildMatrix(sourceRectangle);
350+
return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace);
360351
}
361352

362353
private static void CheckDegenerate(Matrix3x2 matrix)

src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ public void ApplyTransform<TResampler>(in TResampler sampler)
6161
if (matrix.Equals(Matrix3x2.Identity))
6262
{
6363
// The clone will be blank here copy all the pixel data over
64-
var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds());
64+
Rectangle interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds());
6565
Buffer2DRegion<TPixel> sourceBuffer = source.PixelBuffer.GetRegion(interest);
66-
Buffer2DRegion<TPixel> destbuffer = destination.PixelBuffer.GetRegion(interest);
66+
Buffer2DRegion<TPixel> destinationBuffer = destination.PixelBuffer.GetRegion(interest);
6767
for (int y = 0; y < sourceBuffer.Height; y++)
6868
{
69-
sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y));
69+
sourceBuffer.DangerousGetRowSpan(y).CopyTo(destinationBuffer.DangerousGetRowSpan(y));
7070
}
7171

7272
return;
@@ -77,7 +77,7 @@ public void ApplyTransform<TResampler>(in TResampler sampler)
7777

7878
if (sampler is NearestNeighborResampler)
7979
{
80-
var nnOperation = new NNAffineOperation(
80+
NNAffineOperation nnOperation = new(
8181
source.PixelBuffer,
8282
Rectangle.Intersect(this.SourceRectangle, source.Bounds()),
8383
destination.PixelBuffer,
@@ -91,7 +91,7 @@ public void ApplyTransform<TResampler>(in TResampler sampler)
9191
return;
9292
}
9393

94-
var operation = new AffineOperation<TResampler>(
94+
AffineOperation<TResampler> operation = new(
9595
configuration,
9696
source.PixelBuffer,
9797
Rectangle.Intersect(this.SourceRectangle, source.Bounds()),
@@ -128,17 +128,17 @@ public NNAffineOperation(
128128
[MethodImpl(InliningOptions.ShortMethod)]
129129
public void Invoke(int y)
130130
{
131-
Span<TPixel> destRow = this.destination.DangerousGetRowSpan(y);
131+
Span<TPixel> destinationRowSpan = this.destination.DangerousGetRowSpan(y);
132132

133-
for (int x = 0; x < destRow.Length; x++)
133+
for (int x = 0; x < destinationRowSpan.Length; x++)
134134
{
135-
var point = Vector2.Transform(new Vector2(x, y), this.matrix);
135+
Vector2 point = Vector2.Transform(new Vector2(x, y), this.matrix);
136136
int px = (int)MathF.Round(point.X);
137137
int py = (int)MathF.Round(point.Y);
138138

139139
if (this.bounds.Contains(px, py))
140140
{
141-
destRow[x] = this.source.GetElementUnsafe(px, py);
141+
destinationRowSpan[x] = this.source.GetElementUnsafe(px, py);
142142
}
143143
}
144144
}
@@ -195,16 +195,16 @@ public void Invoke(in RowInterval rows, Span<Vector4> span)
195195

196196
for (int y = rows.Min; y < rows.Max; y++)
197197
{
198-
Span<TPixel> rowSpan = this.destination.DangerousGetRowSpan(y);
198+
Span<TPixel> destinationRowSpan = this.destination.DangerousGetRowSpan(y);
199199
PixelOperations<TPixel>.Instance.ToVector4(
200200
this.configuration,
201-
rowSpan,
201+
destinationRowSpan,
202202
span,
203203
PixelConversionModifiers.Scale);
204204

205205
for (int x = 0; x < span.Length; x++)
206206
{
207-
var point = Vector2.Transform(new Vector2(x, y), matrix);
207+
Vector2 point = Vector2.Transform(new Vector2(x, y), matrix);
208208
float pY = point.Y;
209209
float pX = point.X;
210210

@@ -221,13 +221,14 @@ public void Invoke(in RowInterval rows, Span<Vector4> span)
221221
Vector4 sum = Vector4.Zero;
222222
for (int yK = top; yK <= bottom; yK++)
223223
{
224+
Span<TPixel> sourceRowSpan = this.source.DangerousGetRowSpan(yK);
224225
float yWeight = sampler.GetValue(yK - pY);
225226

226227
for (int xK = left; xK <= right; xK++)
227228
{
228229
float xWeight = sampler.GetValue(xK - pX);
229230

230-
Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4();
231+
Vector4 current = sourceRowSpan[xK].ToScaledVector4();
231232
Numerics.Premultiply(ref current);
232233
sum += current * xWeight * yWeight;
233234
}
@@ -240,7 +241,7 @@ public void Invoke(in RowInterval rows, Span<Vector4> span)
240241
PixelOperations<TPixel>.Instance.FromVector4Destructive(
241242
this.configuration,
242243
span,
243-
rowSpan,
244+
destinationRowSpan,
244245
PixelConversionModifiers.Scale);
245246
}
246247
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Processing.Processors.Transforms.Linear;
5+
6+
/// <summary>
7+
/// Represents a solver for systems of linear equations using the Gaussian Elimination method.
8+
/// This class applies Gaussian Elimination to transform the matrix into row echelon form and then performs back substitution to find the solution vector.
9+
/// This implementation is based on: <see href="https://www.algorithm-archive.org/contents/gaussian_elimination/gaussian_elimination.html"/>
10+
/// </summary>
11+
internal static class GaussianEliminationSolver
12+
{
13+
/// <summary>
14+
/// Solves the system of linear equations represented by the given matrix and result vector using Gaussian Elimination.
15+
/// </summary>
16+
/// <param name="matrix">The square matrix representing the coefficients of the linear equations.</param>
17+
/// <param name="result">The vector representing the constants on the right-hand side of the linear equations.</param>
18+
/// <exception cref="Exception">Thrown if the matrix is singular and cannot be solved.</exception>
19+
/// <remarks>
20+
/// The matrix passed to this method must be a square matrix.
21+
/// If the matrix is singular (i.e., has no unique solution), an <see cref="NotSupportedException"/> will be thrown.
22+
/// </remarks>
23+
public static void Solve(double[][] matrix, double[] result)
24+
{
25+
TransformToRowEchelonForm(matrix, result);
26+
ApplyBackSubstitution(matrix, result);
27+
}
28+
29+
private static void TransformToRowEchelonForm(double[][] matrix, double[] result)
30+
{
31+
int colCount = matrix.Length;
32+
int rowCount = matrix[0].Length;
33+
int pivotRow = 0;
34+
for (int pivotCol = 0; pivotCol < colCount; pivotCol++)
35+
{
36+
double maxValue = double.Abs(matrix[pivotRow][pivotCol]);
37+
int maxIndex = pivotRow;
38+
for (int r = pivotRow + 1; r < rowCount; r++)
39+
{
40+
double value = double.Abs(matrix[r][pivotCol]);
41+
if (value > maxValue)
42+
{
43+
maxIndex = r;
44+
maxValue = value;
45+
}
46+
}
47+
48+
if (matrix[maxIndex][pivotCol] == 0)
49+
{
50+
throw new NotSupportedException("Matrix is singular and cannot be solve");
51+
}
52+
53+
(matrix[pivotRow], matrix[maxIndex]) = (matrix[maxIndex], matrix[pivotRow]);
54+
(result[pivotRow], result[maxIndex]) = (result[maxIndex], result[pivotRow]);
55+
56+
for (int r = pivotRow + 1; r < rowCount; r++)
57+
{
58+
double fraction = matrix[r][pivotCol] / matrix[pivotRow][pivotCol];
59+
for (int c = pivotCol + 1; c < colCount; c++)
60+
{
61+
matrix[r][c] -= matrix[pivotRow][c] * fraction;
62+
}
63+
64+
result[r] -= result[pivotRow] * fraction;
65+
matrix[r][pivotCol] = 0;
66+
}
67+
68+
pivotRow++;
69+
}
70+
}
71+
72+
private static void ApplyBackSubstitution(double[][] matrix, double[] result)
73+
{
74+
int rowCount = matrix[0].Length;
75+
76+
for (int row = rowCount - 1; row >= 0; row--)
77+
{
78+
result[row] /= matrix[row][row];
79+
80+
for (int r = 0; r < row; r++)
81+
{
82+
result[r] -= result[row] * matrix[r][row];
83+
}
84+
}
85+
}
86+
}

src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ public void ApplyTransform<TResampler>(in TResampler sampler)
6161
if (matrix.Equals(Matrix4x4.Identity))
6262
{
6363
// The clone will be blank here copy all the pixel data over
64-
var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds());
64+
Rectangle interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds());
6565
Buffer2DRegion<TPixel> sourceBuffer = source.PixelBuffer.GetRegion(interest);
66-
Buffer2DRegion<TPixel> destbuffer = destination.PixelBuffer.GetRegion(interest);
66+
Buffer2DRegion<TPixel> destinationBuffer = destination.PixelBuffer.GetRegion(interest);
6767
for (int y = 0; y < sourceBuffer.Height; y++)
6868
{
69-
sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y));
69+
sourceBuffer.DangerousGetRowSpan(y).CopyTo(destinationBuffer.DangerousGetRowSpan(y));
7070
}
7171

7272
return;
@@ -77,7 +77,7 @@ public void ApplyTransform<TResampler>(in TResampler sampler)
7777

7878
if (sampler is NearestNeighborResampler)
7979
{
80-
var nnOperation = new NNProjectiveOperation(
80+
NNProjectiveOperation nnOperation = new(
8181
source.PixelBuffer,
8282
Rectangle.Intersect(this.SourceRectangle, source.Bounds()),
8383
destination.PixelBuffer,
@@ -91,7 +91,7 @@ public void ApplyTransform<TResampler>(in TResampler sampler)
9191
return;
9292
}
9393

94-
var operation = new ProjectiveOperation<TResampler>(
94+
ProjectiveOperation<TResampler> operation = new(
9595
configuration,
9696
source.PixelBuffer,
9797
Rectangle.Intersect(this.SourceRectangle, source.Bounds()),
@@ -128,17 +128,17 @@ public NNProjectiveOperation(
128128
[MethodImpl(InliningOptions.ShortMethod)]
129129
public void Invoke(int y)
130130
{
131-
Span<TPixel> destRow = this.destination.DangerousGetRowSpan(y);
131+
Span<TPixel> destinationRowSpan = this.destination.DangerousGetRowSpan(y);
132132

133-
for (int x = 0; x < destRow.Length; x++)
133+
for (int x = 0; x < destinationRowSpan.Length; x++)
134134
{
135135
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix);
136136
int px = (int)MathF.Round(point.X);
137137
int py = (int)MathF.Round(point.Y);
138138

139139
if (this.bounds.Contains(px, py))
140140
{
141-
destRow[x] = this.source.GetElementUnsafe(px, py);
141+
destinationRowSpan[x] = this.source.GetElementUnsafe(px, py);
142142
}
143143
}
144144
}
@@ -195,10 +195,10 @@ public void Invoke(in RowInterval rows, Span<Vector4> span)
195195

196196
for (int y = rows.Min; y < rows.Max; y++)
197197
{
198-
Span<TPixel> rowSpan = this.destination.DangerousGetRowSpan(y);
198+
Span<TPixel> destinationRowSpan = this.destination.DangerousGetRowSpan(y);
199199
PixelOperations<TPixel>.Instance.ToVector4(
200200
this.configuration,
201-
rowSpan,
201+
destinationRowSpan,
202202
span,
203203
PixelConversionModifiers.Scale);
204204

@@ -221,13 +221,14 @@ public void Invoke(in RowInterval rows, Span<Vector4> span)
221221
Vector4 sum = Vector4.Zero;
222222
for (int yK = top; yK <= bottom; yK++)
223223
{
224+
Span<TPixel> sourceRowSpan = this.source.DangerousGetRowSpan(yK);
224225
float yWeight = sampler.GetValue(yK - pY);
225226

226227
for (int xK = left; xK <= right; xK++)
227228
{
228229
float xWeight = sampler.GetValue(xK - pX);
229230

230-
Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4();
231+
Vector4 current = sourceRowSpan[xK].ToScaledVector4();
231232
Numerics.Premultiply(ref current);
232233
sum += current * xWeight * yWeight;
233234
}
@@ -240,7 +241,7 @@ public void Invoke(in RowInterval rows, Span<Vector4> span)
240241
PixelOperations<TPixel>.Instance.FromVector4Destructive(
241242
this.configuration,
242243
span,
243-
rowSpan,
244+
destinationRowSpan,
244245
PixelConversionModifiers.Scale);
245246
}
246247
}

0 commit comments

Comments
 (0)