From 88e59a14aae9670f7584eb4cf241b172ba0c8b02 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 18 Jan 2022 18:05:33 +1100 Subject: [PATCH 1/9] Remove processor exception wrapping. #1827 --- .../CloningImageProcessor{TPixel}.cs | 41 +++++++------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs index 2a41329a63..3e420ca03b 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs @@ -46,38 +46,25 @@ protected CloningImageProcessor(Configuration configuration, Image sourc /// Image ICloningImageProcessor.CloneAndExecute() { - try - { - Image clone = this.CreateTarget(); - this.CheckFrameCount(this.Source, clone); + Image clone = this.CreateTarget(); + this.CheckFrameCount(this.Source, clone); - Configuration configuration = this.Configuration; - this.BeforeImageApply(clone); + Configuration configuration = this.Configuration; + this.BeforeImageApply(clone); - for (int i = 0; i < this.Source.Frames.Count; i++) - { - ImageFrame sourceFrame = this.Source.Frames[i]; - ImageFrame clonedFrame = clone.Frames[i]; + for (int i = 0; i < this.Source.Frames.Count; i++) + { + ImageFrame sourceFrame = this.Source.Frames[i]; + ImageFrame clonedFrame = clone.Frames[i]; - this.BeforeFrameApply(sourceFrame, clonedFrame); - this.OnFrameApply(sourceFrame, clonedFrame); - this.AfterFrameApply(sourceFrame, clonedFrame); - } + this.BeforeFrameApply(sourceFrame, clonedFrame); + this.OnFrameApply(sourceFrame, clonedFrame); + this.AfterFrameApply(sourceFrame, clonedFrame); + } - this.AfterImageApply(clone); + this.AfterImageApply(clone); - return clone; - } -#if DEBUG - catch (Exception) - { - throw; -#else - catch (Exception ex) - { - throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); -#endif - } + return clone; } /// From 9a2ca51e0285176f9d7e6a899eba048e4344f8ef Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 18 Jan 2022 18:06:21 +1100 Subject: [PATCH 2/9] Rename Buffer2DRegion GetRowSpan --- src/ImageSharp/Memory/Buffer2DRegion{T}.cs | 4 ++-- .../Processing/Processors/Transforms/Resize/ResizeWorker.cs | 2 +- tests/ImageSharp.Tests/Memory/BufferAreaTests.cs | 2 +- .../Quantization/PixelSamplingStrategyTests.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs index d1c39ccbf5..13b3395977 100644 --- a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs +++ b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs @@ -88,7 +88,7 @@ public Buffer2DRegion(Buffer2D buffer) /// The row index /// The span [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetRowSpan(int y) + public Span DangerousGetRowSpan(int y) { int yy = this.Rectangle.Y + y; int xx = this.Rectangle.X; @@ -152,7 +152,7 @@ internal void Clear() for (int y = 0; y < this.Rectangle.Height; y++) { - Span row = this.GetRowSpan(y); + Span row = this.DangerousGetRowSpan(y); row.Clear(); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 4e3a08c393..b0a8b7cdc4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -171,7 +171,7 @@ private void CalculateFirstPassValues(RowInterval calculationInterval) for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { - Span sourceRow = this.source.GetRowSpan(y); + Span sourceRow = this.source.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index 76e55aa3a1..12b7c74abb 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -68,7 +68,7 @@ public void GetRowSpan(int bufferCapacity, int rx, int ry, int y, int w, int h) Buffer2DRegion region = buffer.GetRegion(r); - Span span = region.GetRowSpan(y); + Span span = region.DangerousGetRowSpan(y); Assert.Equal(w, span.Length); diff --git a/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs b/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs index 9a8d8351b2..7529b0e5f4 100644 --- a/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs +++ b/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs @@ -75,7 +75,7 @@ private static void PaintWhite(Buffer2DRegion region) var white = new L8(255); for (int y = 0; y < region.Height; y++) { - region.GetRowSpan(y).Fill(white); + region.DangerousGetRowSpan(y).Fill(white); } } From b97ac35d5b9514beb92979de4edfc43bb1090c31 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 18 Jan 2022 18:06:40 +1100 Subject: [PATCH 3/9] Apply fix and tests --- .../Linear/AffineTransformProcessor{TPixel}.cs | 9 ++++++++- .../Linear/ProjectiveTransformProcessor{TPixel}.cs | 9 ++++++++- .../Processors/Transforms/AffineTransformTests.cs | 11 +++++++++++ .../Processing/Transforms/ProjectiveTransformTests.cs | 10 ++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 640527fe7c..f77d700cbd 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -64,7 +64,14 @@ public void ApplyTransform(in TResampler sampler) if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) { // The clone will be blank here copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); + Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); + Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); + for (int y = 0; y < sourceBuffer.Height; y++) + { + sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); + } + return; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index cf6567629f..6ed25d4e7e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -63,7 +63,14 @@ public void ApplyTransform(in TResampler sampler) if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) { // The clone will be blank here copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); + Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); + Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); + for (int y = 0; y < sourceBuffer.Height; y++) + { + sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); + } + return; } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs index 3cd8cec154..5ba8f805d3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -4,6 +4,7 @@ using System; using System.Numerics; using System.Reflection; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -224,6 +225,16 @@ public void WorksWithDiscoBuffers(TestImageProvider provider, in c => c.Transform(builder)); } + [Fact] + public void Issue1911() + { + using var image = new Image(100, 100); + image.Mutate(x => x = x.Transform(new Rectangle(0, 0, 99, 100), Matrix3x2.Identity, new Size(99, 100), KnownResamplers.Lanczos2)); + + Assert.Equal(99, image.Width); + Assert.Equal(100, image.Height); + } + private static IResampler GetResampler(string name) { PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index ef8e03763c..4d925bccbf 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -147,6 +147,16 @@ public void PerspectiveTransformMatchesCSS(TestImageProvider pro } } + [Fact] + public void Issue1911() + { + using var image = new Image(100, 100); + image.Mutate(x => x = x.Transform(new Rectangle(0, 0, 99, 100), Matrix4x4.Identity, new Size(99, 100), KnownResamplers.Lanczos2)); + + Assert.Equal(99, image.Width); + Assert.Equal(100, image.Height); + } + private static IResampler GetResampler(string name) { PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); From 6567726efcac623fe0303b593e18d9e14da46b84 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 19 Jan 2022 12:53:25 +1100 Subject: [PATCH 4/9] Explicitly throw for degenerate matrices. --- .../Processors/Transforms/Linear/AffineTransformProcessor.cs | 5 +++++ .../Transforms/Linear/AffineTransformProcessor{TPixel}.cs | 3 ++- .../Transforms/Linear/ProjectiveTransformProcessor.cs | 5 +++++ .../Linear/ProjectiveTransformProcessor{TPixel}.cs | 3 ++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs index 77ba9582d6..75f82a25a6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs @@ -21,6 +21,11 @@ public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targe Guard.NotNull(sampler, nameof(sampler)); Guard.MustBeValueType(sampler, nameof(sampler)); + if (TransformUtils.IsDegenerate(matrix)) + { + throw new DegenerateTransformException("Matrix is degenerate. Check input values."); + } + this.Sampler = sampler; this.TransformMatrix = matrix; this.DestinationSize = targetDimensions; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index f77d700cbd..81071dd1dc 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -61,7 +61,8 @@ public void ApplyTransform(in TResampler sampler) Matrix3x2 matrix = this.transformMatrix; // Handle transforms that result in output identical to the original. - if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) + // Degenerate matrices are already handled in the upstream definition. + if (matrix.Equals(Matrix3x2.Identity)) { // The clone will be blank here copy all the pixel data over var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs index 338489d3f3..5eb89fe8ae 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs @@ -21,6 +21,11 @@ public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size t Guard.NotNull(sampler, nameof(sampler)); Guard.MustBeValueType(sampler, nameof(sampler)); + if (TransformUtils.IsDegenerate(matrix)) + { + throw new DegenerateTransformException("Matrix is degenerate. Check input values."); + } + this.Sampler = sampler; this.TransformMatrix = matrix; this.DestinationSize = targetDimensions; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index 6ed25d4e7e..96e51afa8d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -60,7 +60,8 @@ public void ApplyTransform(in TResampler sampler) Matrix4x4 matrix = this.transformMatrix; // Handle transforms that result in output identical to the original. - if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) + // Degenerate matrices are already handled in the upstream definition. + if (matrix.Equals(Matrix4x4.Identity)) { // The clone will be blank here copy all the pixel data over var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); From ddb81921e203277abf3fcb9ff83f70c68f117350 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Jan 2022 00:15:17 +1100 Subject: [PATCH 5/9] Fix source rectangle handling --- .../AffineTransformProcessor{TPixel}.cs | 42 +++++++++++------ .../Linear/LinearTransformUtility.cs | 10 ++-- .../ProjectiveTransformProcessor{TPixel}.cs | 46 ++++++++++++------- 3 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 81071dd1dc..beb14ad019 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -81,7 +81,12 @@ public void ApplyTransform(in TResampler sampler) if (sampler is NearestNeighborResampler) { - var nnOperation = new NNAffineOperation(source.PixelBuffer, destination.PixelBuffer, matrix); + var nnOperation = new NNAffineOperation( + source.PixelBuffer, + Rectangle.Intersect(this.SourceRectangle, source.Bounds()), + destination.PixelBuffer, + matrix); + ParallelRowIterator.IterateRows( configuration, destination.Bounds(), @@ -93,6 +98,7 @@ public void ApplyTransform(in TResampler sampler) var operation = new AffineOperation( configuration, source.PixelBuffer, + Rectangle.Intersect(this.SourceRectangle, source.Bounds()), destination.PixelBuffer, in sampler, matrix); @@ -113,12 +119,13 @@ public void ApplyTransform(in TResampler sampler) [MethodImpl(InliningOptions.ShortMethod)] public NNAffineOperation( Buffer2D source, + Rectangle bounds, Buffer2D destination, Matrix3x2 matrix) { this.source = source; + this.bounds = bounds; this.destination = destination; - this.bounds = source.Bounds(); this.matrix = matrix; } @@ -146,6 +153,7 @@ public void Invoke(int y) { private readonly Configuration configuration; private readonly Buffer2D source; + private readonly Rectangle bounds; private readonly Buffer2D destination; private readonly TResampler sampler; private readonly Matrix3x2 matrix; @@ -156,12 +164,14 @@ public void Invoke(int y) public AffineOperation( Configuration configuration, Buffer2D source, + Rectangle bounds, Buffer2D destination, in TResampler sampler, Matrix3x2 matrix) { this.configuration = configuration; this.source = source; + this.bounds = bounds; this.destination = destination; this.sampler = sampler; this.matrix = matrix; @@ -190,8 +200,10 @@ public void Invoke(in RowInterval rows, Span span) TResampler sampler = this.sampler; float yRadius = this.yRadius; float xRadius = this.xRadius; - int maxY = this.source.Height - 1; - int maxX = this.source.Width - 1; + int minY = this.bounds.Y; + int maxY = this.bounds.Right - 1; + int minX = this.bounds.X; + int maxX = this.bounds.Bottom - 1; for (int y = rows.Min; y < rows.Max; y++) { @@ -208,10 +220,10 @@ public void Invoke(in RowInterval rows, Span span) float pY = point.Y; float pX = point.X; - int top = LinearTransformUtility.GetRangeStart(yRadius, pY, maxY); - int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, maxY); - int left = LinearTransformUtility.GetRangeStart(xRadius, pX, maxX); - int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, maxX); + int top = LinearTransformUtility.GetRangeStart(yRadius, pY, minY, maxY); + int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, minY, maxY); + int left = LinearTransformUtility.GetRangeStart(xRadius, pX, minX, maxX); + int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, minX, maxX); if (bottom == top || right == left) { @@ -253,8 +265,10 @@ private void InvokeMacOSX(in RowInterval rows, Span span) TResampler sampler = this.sampler; float yRadius = this.yRadius; float xRadius = this.xRadius; - int maxY = this.source.Height - 1; - int maxX = this.source.Width - 1; + int minY = this.bounds.Y; + int maxY = this.bounds.Right - 1; + int minX = this.bounds.X; + int maxX = this.bounds.Bottom - 1; for (int y = rows.Min; y < rows.Max; y++) { @@ -271,10 +285,10 @@ private void InvokeMacOSX(in RowInterval rows, Span span) float pY = point.Y; float pX = point.X; - int top = LinearTransformUtility.GetRangeStart(yRadius, pY, maxY); - int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, maxY); - int left = LinearTransformUtility.GetRangeStart(xRadius, pX, maxX); - int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, maxX); + int top = LinearTransformUtility.GetRangeStart(yRadius, pY, minY, maxY); + int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, minY, maxY); + int left = LinearTransformUtility.GetRangeStart(xRadius, pX, minX, maxX); + int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, minX, maxX); if (bottom == top || right == left) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs index c6168b4619..fd0c7f23bd 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs @@ -39,11 +39,12 @@ public static float GetSamplingRadius(in TResampler sampler, int sou /// /// The radius. /// The center position. + /// The min allowed amouunt. /// The max allowed amouunt. /// The . [MethodImpl(InliningOptions.ShortMethod)] - public static int GetRangeStart(float radius, float center, int max) - => Numerics.Clamp((int)MathF.Ceiling(center - radius), 0, max); + public static int GetRangeStart(float radius, float center, int min, int max) + => Numerics.Clamp((int)MathF.Ceiling(center - radius), min, max); /// /// Gets the end position (inclusive) for a sampling range given @@ -51,10 +52,11 @@ public static int GetRangeStart(float radius, float center, int max) /// /// The radius. /// The center position. + /// The min allowed amouunt. /// The max allowed amouunt. /// The . [MethodImpl(InliningOptions.ShortMethod)] - public static int GetRangeEnd(float radius, float center, int max) - => Numerics.Clamp((int)MathF.Floor(center + radius), 0, max); + public static int GetRangeEnd(float radius, float center, int min, int max) + => Numerics.Clamp((int)MathF.Floor(center + radius), min, max); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index 96e51afa8d..00bb074001 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -80,7 +80,12 @@ public void ApplyTransform(in TResampler sampler) if (sampler is NearestNeighborResampler) { - var nnOperation = new NNProjectiveOperation(source.PixelBuffer, destination.PixelBuffer, matrix); + var nnOperation = new NNProjectiveOperation( + source.PixelBuffer, + Rectangle.Intersect(this.SourceRectangle, source.Bounds()), + destination.PixelBuffer, + matrix); + ParallelRowIterator.IterateRows( configuration, destination.Bounds(), @@ -92,6 +97,7 @@ public void ApplyTransform(in TResampler sampler) var operation = new ProjectiveOperation( configuration, source.PixelBuffer, + Rectangle.Intersect(this.SourceRectangle, source.Bounds()), destination.PixelBuffer, in sampler, matrix); @@ -112,12 +118,13 @@ public void ApplyTransform(in TResampler sampler) [MethodImpl(InliningOptions.ShortMethod)] public NNProjectiveOperation( Buffer2D source, + Rectangle bounds, Buffer2D destination, Matrix4x4 matrix) { this.source = source; + this.bounds = bounds; this.destination = destination; - this.bounds = source.Bounds(); this.matrix = matrix; } @@ -145,6 +152,7 @@ public void Invoke(int y) { private readonly Configuration configuration; private readonly Buffer2D source; + private readonly Rectangle bounds; private readonly Buffer2D destination; private readonly TResampler sampler; private readonly Matrix4x4 matrix; @@ -155,18 +163,20 @@ public void Invoke(int y) public ProjectiveOperation( Configuration configuration, Buffer2D source, + Rectangle bounds, Buffer2D destination, in TResampler sampler, Matrix4x4 matrix) { this.configuration = configuration; this.source = source; + this.bounds = bounds; this.destination = destination; this.sampler = sampler; this.matrix = matrix; - this.yRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Height, destination.Height); - this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Width, destination.Width); + this.yRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Height, destination.Height); + this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, bounds.Width, destination.Width); } [MethodImpl(InliningOptions.ShortMethod)] @@ -189,8 +199,10 @@ public void Invoke(in RowInterval rows, Span span) TResampler sampler = this.sampler; float yRadius = this.yRadius; float xRadius = this.xRadius; - int maxY = this.source.Height - 1; - int maxX = this.source.Width - 1; + int minY = this.bounds.Y; + int maxY = this.bounds.Right - 1; + int minX = this.bounds.X; + int maxX = this.bounds.Bottom - 1; for (int y = rows.Min; y < rows.Max; y++) { @@ -207,10 +219,10 @@ public void Invoke(in RowInterval rows, Span span) float pY = point.Y; float pX = point.X; - int top = LinearTransformUtility.GetRangeStart(yRadius, pY, maxY); - int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, maxY); - int left = LinearTransformUtility.GetRangeStart(xRadius, pX, maxX); - int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, maxX); + int top = LinearTransformUtility.GetRangeStart(yRadius, pY, minY, maxY); + int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, minY, maxY); + int left = LinearTransformUtility.GetRangeStart(xRadius, pX, minX, maxX); + int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, minX, maxX); if (bottom <= top || right <= left) { @@ -252,8 +264,10 @@ public void InvokeMacOSX(in RowInterval rows, Span span) TResampler sampler = this.sampler; float yRadius = this.yRadius; float xRadius = this.xRadius; - int maxY = this.source.Height - 1; - int maxX = this.source.Width - 1; + int minY = this.bounds.Y; + int maxY = this.bounds.Right - 1; + int minX = this.bounds.X; + int maxX = this.bounds.Bottom - 1; for (int y = rows.Min; y < rows.Max; y++) { @@ -270,10 +284,10 @@ public void InvokeMacOSX(in RowInterval rows, Span span) float pY = point.Y; float pX = point.X; - int top = LinearTransformUtility.GetRangeStart(yRadius, pY, maxY); - int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, maxY); - int left = LinearTransformUtility.GetRangeStart(xRadius, pX, maxX); - int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, maxX); + int top = LinearTransformUtility.GetRangeStart(yRadius, pY, minY, maxY); + int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, minY, maxY); + int left = LinearTransformUtility.GetRangeStart(xRadius, pX, minX, maxX); + int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, minX, maxX); if (bottom <= top || right <= left) { From 180735017650fdd922b93203a1ae12c81f6be377 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Jan 2022 00:41:42 +1100 Subject: [PATCH 6/9] Fix switched assignments --- .../Transforms/Linear/AffineTransformProcessor{TPixel}.cs | 4 ++-- .../Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index beb14ad019..1cd2514659 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -201,9 +201,9 @@ public void Invoke(in RowInterval rows, Span span) float yRadius = this.yRadius; float xRadius = this.xRadius; int minY = this.bounds.Y; - int maxY = this.bounds.Right - 1; + int maxY = this.bounds.Bottom - 1; int minX = this.bounds.X; - int maxX = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; for (int y = rows.Min; y < rows.Max; y++) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index 00bb074001..fc2cb0c851 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -200,9 +200,9 @@ public void Invoke(in RowInterval rows, Span span) float yRadius = this.yRadius; float xRadius = this.xRadius; int minY = this.bounds.Y; - int maxY = this.bounds.Right - 1; + int maxY = this.bounds.Bottom - 1; int minX = this.bounds.X; - int maxX = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; for (int y = rows.Min; y < rows.Max; y++) { From ea4a284b891acc61e5fb623a975d55cc33201be2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Jan 2022 01:03:26 +1100 Subject: [PATCH 7/9] Fix source rectangle test refs --- ...Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png | 4 ++-- ...Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png index f5af011026..53ac0ff89f 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b01d54838d678b61c3b7a1c7e76ff9a60b3f5f4faef5af848231177eac956eb1 -size 1262 +oid sha256:bbe1ffaf7b801fd92724438cc810fd0c5506e0a907b970c4f0bf5bec3627ca2a +size 551 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png index e5005ac5d4..2480164d60 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d34394771605c2a70cc23f3841592c20d22a68aaabf2ad6e8aba7348a181afb3 -size 531 +oid sha256:b45933471a1af1b6d4112240e1bc6b6187065a872043ddbf917200ce9e8cc84b +size 371 From f2f719985dc2556c403a36f8e93c7c35d7ad8a83 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Jan 2022 01:22:54 +1100 Subject: [PATCH 8/9] Mac fixes --- .../Transforms/Linear/AffineTransformProcessor{TPixel}.cs | 4 ++-- .../Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 1cd2514659..f6fadca33e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -266,9 +266,9 @@ private void InvokeMacOSX(in RowInterval rows, Span span) float yRadius = this.yRadius; float xRadius = this.xRadius; int minY = this.bounds.Y; - int maxY = this.bounds.Right - 1; + int maxY = this.bounds.Bottom - 1; int minX = this.bounds.X; - int maxX = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; for (int y = rows.Min; y < rows.Max; y++) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index fc2cb0c851..26b970d8b5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -265,9 +265,9 @@ public void InvokeMacOSX(in RowInterval rows, Span span) float yRadius = this.yRadius; float xRadius = this.xRadius; int minY = this.bounds.Y; - int maxY = this.bounds.Right - 1; + int maxY = this.bounds.Bottom - 1; int minX = this.bounds.X; - int maxX = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; for (int y = rows.Min; y < rows.Max; y++) { From 05e8d16b3be7724ecb48cbf4803cea97eb0d91e1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 25 Jan 2022 14:07:07 +1100 Subject: [PATCH 9/9] Update tests --- .../Transforms/AffineTransformTests.cs | 30 +++++++++++++++++++ .../Transforms/ProjectiveTransformTests.cs | 30 +++++++++++++++++++ .../Identity_Rgba32_TestPattern100x100.png | 3 ++ ...sions_Rgba32_TestPattern100x100_0.0001.png | 3 ++ ...Dimensions_Rgba32_TestPattern100x100_0.png | 3 ++ ...imensions_Rgba32_TestPattern100x100_57.png | 3 ++ .../Identity_Rgba32_TestPattern100x100.png | 3 ++ ...sions_Rgba32_TestPattern100x100_0.0001.png | 3 ++ ...Dimensions_Rgba32_TestPattern100x100_0.png | 3 ++ ...imensions_Rgba32_TestPattern100x100_57.png | 3 ++ 10 files changed, 84 insertions(+) create mode 100644 tests/Images/External/ReferenceOutput/AffineTransformTests/Identity_Rgba32_TestPattern100x100.png create mode 100644 tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png create mode 100644 tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png create mode 100644 tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png create mode 100644 tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Identity_Rgba32_TestPattern100x100.png create mode 100644 tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png create mode 100644 tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png create mode 100644 tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs index 5ba8f805d3..33725f7aa5 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -235,6 +235,36 @@ public void Issue1911() Assert.Equal(100, image.Height); } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void Identity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + Matrix3x2 m = Matrix3x2.Identity; + Rectangle r = new(25, 25, 50, 50); + image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001F)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 57F)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0F)] + public void Transform_With_Custom_Dimensions(TestImageProvider provider, float radians) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + var m = Matrix3x2.CreateRotation(radians, new Vector2(50, 50)); + Rectangle r = new(25, 25, 50, 50); + image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); + image.DebugSave(provider, testOutputDetails: radians); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians); + } + private static IResampler GetResampler(string name) { PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index 4d925bccbf..d841c963f6 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -157,6 +157,36 @@ public void Issue1911() Assert.Equal(100, image.Height); } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void Identity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + Matrix4x4 m = Matrix4x4.Identity; + Rectangle r = new(25, 25, 50, 50); + image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); + image.DebugSave(provider); + image.CompareToReferenceOutput(ValidatorComparer, provider); + } + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0.0001F)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 57F)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 0F)] + public void Transform_With_Custom_Dimensions(TestImageProvider provider, float radians) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + Matrix4x4 m = Matrix4x4.CreateRotationX(radians, new Vector3(50, 50, 1F)) * Matrix4x4.CreateRotationY(radians, new Vector3(50, 50, 1F)); + Rectangle r = new(25, 25, 50, 50); + image.Mutate(x => x.Transform(r, m, new Size(100, 100), KnownResamplers.Bicubic)); + image.DebugSave(provider, testOutputDetails: radians); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians); + } + private static IResampler GetResampler(string name) { PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Identity_Rgba32_TestPattern100x100.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Identity_Rgba32_TestPattern100x100.png new file mode 100644 index 0000000000..ce6e8ce9fa --- /dev/null +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Identity_Rgba32_TestPattern100x100.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da8229605bda413676a42f587df250a743540e6e00c04eacb1e622f223e19595 +size 3564 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png new file mode 100644 index 0000000000..4b9953b670 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd3b29b530e221618f65cd5e493b21fe3c27804fde7664636b7bb002f72abbb2 +size 3663 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png new file mode 100644 index 0000000000..ce6e8ce9fa --- /dev/null +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da8229605bda413676a42f587df250a743540e6e00c04eacb1e622f223e19595 +size 3564 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png new file mode 100644 index 0000000000..5f4911e47c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a35757fef08a6fd9b37e719d5be7a82d5ff79f0395e082f697d9ebe9c7f03cc8 +size 5748 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Identity_Rgba32_TestPattern100x100.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Identity_Rgba32_TestPattern100x100.png new file mode 100644 index 0000000000..ce6e8ce9fa --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Identity_Rgba32_TestPattern100x100.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da8229605bda413676a42f587df250a743540e6e00c04eacb1e622f223e19595 +size 3564 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png new file mode 100644 index 0000000000..e8efa8a980 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39c25539c3c9b8926bf65c041df693a60617bbe8653bb72357bde5ab6342c59c +size 3618 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png new file mode 100644 index 0000000000..ce6e8ce9fa --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da8229605bda413676a42f587df250a743540e6e00c04eacb1e622f223e19595 +size 3564 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png new file mode 100644 index 0000000000..99a74e400a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b1fc95fdf07c7443147205afffb157aa82f94818cfbb833a615c42f584fbda0 +size 5070