Skip to content

Commit

Permalink
Merge pull request #1181 from SixLabors/js/fix-966
Browse files Browse the repository at this point in the history
Add DegenerateTransformException
  • Loading branch information
JimBobSquarePants committed Apr 24, 2020
2 parents 06721fd + b1afae8 commit 2320f16
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 46 deletions.
11 changes: 9 additions & 2 deletions src/ImageSharp/Common/Exceptions/ImageProcessingException.cs
@@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
Expand All @@ -10,6 +10,13 @@ namespace SixLabors.ImageSharp
/// </summary>
public sealed class ImageProcessingException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageProcessingException"/> class.
/// </summary>
public ImageProcessingException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ImageProcessingException"/> class with the name of the
/// parameter that causes this exception.
Expand All @@ -32,4 +39,4 @@ public ImageProcessingException(string errorMessage, Exception innerException)
{
}
}
}
}
41 changes: 37 additions & 4 deletions src/ImageSharp/Processing/AffineTransformBuilder.cs
@@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
Expand Down Expand Up @@ -247,15 +247,33 @@ public AffineTransformBuilder AppendTranslation(Vector2 position)
/// Prepends a raw matrix.
/// </summary>
/// <param name="matrix">The matrix to prepend.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix) => this.Prepend(_ => matrix);
public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix)
{
CheckDegenerate(matrix);
return this.Prepend(_ => matrix);
}

/// <summary>
/// Appends a raw matrix.
/// </summary>
/// <param name="matrix">The matrix to append.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix) => this.Append(_ => matrix);
public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix)
{
CheckDegenerate(matrix);
return this.Append(_ => matrix);
}

/// <summary>
/// Returns the combined matrix for a given source size.
Expand All @@ -268,6 +286,11 @@ public AffineTransformBuilder AppendTranslation(Vector2 position)
/// Returns the combined matrix for a given source rectangle.
/// </summary>
/// <param name="sourceRectangle">The rectangle in the source image.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
public Matrix3x2 BuildMatrix(Rectangle sourceRectangle)
{
Expand All @@ -284,9 +307,19 @@ public Matrix3x2 BuildMatrix(Rectangle sourceRectangle)
matrix *= factory(size);
}

CheckDegenerate(matrix);

return matrix;
}

private static void CheckDegenerate(Matrix3x2 matrix)
{
if (TransformUtilities.IsDegenerate(matrix))
{
throw new DegenerateTransformException("Matrix is degenerate. Check input values.");
}
}

private AffineTransformBuilder Prepend(Func<Size, Matrix3x2> factory)
{
this.matrixFactories.Insert(0, factory);
Expand All @@ -299,4 +332,4 @@ private AffineTransformBuilder Append(Func<Size, Matrix3x2> factory)
return this;
}
}
}
}
@@ -0,0 +1,42 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;

namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Represents an error that occurs during a transform operation.
/// </summary>
public sealed class DegenerateTransformException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="DegenerateTransformException"/> class.
/// </summary>
public DegenerateTransformException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DegenerateTransformException" /> class
/// with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public DegenerateTransformException(string message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DegenerateTransformException" /> class
/// with a specified error message and a reference to the inner exception that is
/// the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public DegenerateTransformException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
Expand Up @@ -12,6 +12,57 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
internal static class TransformUtilities
{
/// <summary>
/// Returns a value that indicates whether the specified matrix is degenerate
/// containing one or more values equivalent to <see cref="float.NaN"/> or a
/// zero determinant and therefore cannot be used for linear transforms.
/// </summary>
/// <param name="matrix">The transform matrix.</param>
public static bool IsDegenerate(Matrix3x2 matrix)
=> IsNaN(matrix) || IsZero(matrix.GetDeterminant());

/// <summary>
/// Returns a value that indicates whether the specified matrix is degenerate
/// containing one or more values equivalent to <see cref="float.NaN"/> or a
/// zero determinant and therefore cannot be used for linear transforms.
/// </summary>
/// <param name="matrix">The transform matrix.</param>
public static bool IsDegenerate(Matrix4x4 matrix)
=> IsNaN(matrix) || IsZero(matrix.GetDeterminant());

[MethodImpl(InliningOptions.ShortMethod)]
private static bool IsZero(float a)
=> a > -Constants.EpsilonSquared && a < Constants.EpsilonSquared;

/// <summary>
/// Returns a value that indicates whether the specified matrix contains any values
/// that are not a number <see cref="float.NaN"/>.
/// </summary>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static bool IsNaN(Matrix3x2 matrix)
{
return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32);
}

/// <summary>
/// Returns a value that indicates whether the specified matrix contains any values
/// that are not a number <see cref="float.NaN"/>.
/// </summary>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static bool IsNaN(Matrix4x4 matrix)
{
return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) || float.IsNaN(matrix.M13) || float.IsNaN(matrix.M14)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) || float.IsNaN(matrix.M23) || float.IsNaN(matrix.M24)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32) || float.IsNaN(matrix.M33) || float.IsNaN(matrix.M34)
|| float.IsNaN(matrix.M41) || float.IsNaN(matrix.M42) || float.IsNaN(matrix.M43) || float.IsNaN(matrix.M44);
}

/// <summary>
/// Applies the projective transform against the given coordinates flattened into the 2D space.
/// </summary>
Expand Down
45 changes: 40 additions & 5 deletions src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
@@ -1,9 +1,10 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Processing.Processors.Transforms;

namespace SixLabors.ImageSharp.Processing
Expand Down Expand Up @@ -263,27 +264,51 @@ public ProjectiveTransformBuilder AppendTranslation(Vector2 position)
/// Prepends a raw matrix.
/// </summary>
/// <param name="matrix">The matrix to prepend.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix) => this.Prepend(_ => matrix);
public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix)
{
CheckDegenerate(matrix);
return this.Prepend(_ => matrix);
}

/// <summary>
/// Appends a raw matrix.
/// </summary>
/// <param name="matrix">The matrix to append.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix) => this.Append(_ => matrix);
public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix)
{
CheckDegenerate(matrix);
return this.Append(_ => matrix);
}

/// <summary>
/// Returns the combined matrix for a given source size.
/// </summary>
/// <param name="sourceSize">The source image size.</param>
/// <returns>The <see cref="Matrix4x4"/>.</returns>
public Matrix4x4 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize));
public Matrix4x4 BuildMatrix(Size sourceSize)
=> this.BuildMatrix(new Rectangle(Point.Empty, sourceSize));

/// <summary>
/// Returns the combined matrix for a given source rectangle.
/// </summary>
/// <param name="sourceRectangle">The rectangle in the source image.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="Matrix4x4"/>.</returns>
public Matrix4x4 BuildMatrix(Rectangle sourceRectangle)
{
Expand All @@ -300,9 +325,19 @@ public Matrix4x4 BuildMatrix(Rectangle sourceRectangle)
matrix *= factory(size);
}

CheckDegenerate(matrix);

return matrix;
}

private static void CheckDegenerate(Matrix4x4 matrix)
{
if (TransformUtilities.IsDegenerate(matrix))
{
throw new DegenerateTransformException("Matrix is degenerate. Check input values.");
}
}

private ProjectiveTransformBuilder Prepend(Func<Size, Matrix4x4> factory)
{
this.matrixFactories.Insert(0, factory);
Expand All @@ -315,4 +350,4 @@ private ProjectiveTransformBuilder Append(Func<Size, Matrix4x4> factory)
return this;
}
}
}
}
16 changes: 4 additions & 12 deletions tests/ImageSharp.Tests/Drawing/DrawImageExtensionsTests.cs
@@ -1,25 +1,17 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Linq;
using Moq;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;

using Xunit;

namespace SixLabors.ImageSharp.Tests.Drawing
{
public class DrawImageExtensionsTests : BaseImageOperationsExtensionTest
{

[Fact]
public void DrawImage_OpacityOnly_VerifyGraphicOptionsTakenFromContext()
{
Expand All @@ -28,7 +20,7 @@ public void DrawImage_OpacityOnly_VerifyGraphicOptionsTakenFromContext()
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;

this.operations.DrawImage(null, 0.5f);
var dip = this.Verify<DrawImageProcessor>();
DrawImageProcessor dip = this.Verify<DrawImageProcessor>();

Assert.Equal(0.5, dip.Opacity);
Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode);
Expand All @@ -43,7 +35,7 @@ public void DrawImage_OpacityAndBlending_VerifyGraphicOptionsTakenFromContext()
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;

this.operations.DrawImage(null, PixelColorBlendingMode.Multiply, 0.5f);
var dip = this.Verify<DrawImageProcessor>();
DrawImageProcessor dip = this.Verify<DrawImageProcessor>();

Assert.Equal(0.5, dip.Opacity);
Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode);
Expand All @@ -58,7 +50,7 @@ public void DrawImage_LocationAndOpacity_VerifyGraphicOptionsTakenFromContext()
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;

this.operations.DrawImage(null, Point.Empty, 0.5f);
var dip = this.Verify<DrawImageProcessor>();
DrawImageProcessor dip = this.Verify<DrawImageProcessor>();

Assert.Equal(0.5, dip.Opacity);
Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode);
Expand All @@ -73,7 +65,7 @@ public void DrawImage_LocationAndOpacityAndBlending_VerifyGraphicOptionsTakenFro
this.options.ColorBlendingMode = PixelColorBlendingMode.Screen;

this.operations.DrawImage(null, Point.Empty, PixelColorBlendingMode.Multiply, 0.5f);
var dip = this.Verify<DrawImageProcessor>();
DrawImageProcessor dip = this.Verify<DrawImageProcessor>();

Assert.Equal(0.5, dip.Opacity);
Assert.Equal(this.options.AlphaCompositionMode, dip.AlphaCompositionMode);
Expand Down
@@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System.Numerics;
Expand All @@ -8,7 +8,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{
public class AffineTransformBuilderTests : TransformBuilderTestBase<AffineTransformBuilder>
{
protected override AffineTransformBuilder CreateBuilder(Rectangle rectangle) => new AffineTransformBuilder();
protected override AffineTransformBuilder CreateBuilder()
=> new AffineTransformBuilder();

protected override void AppendRotationDegrees(AffineTransformBuilder builder, float degrees)
=> builder.AppendRotationDegrees(degrees);
Expand Down Expand Up @@ -67,4 +68,4 @@ protected override void PrependTranslation(AffineTransformBuilder builder, Point
return Vector2.Transform(sourcePoint, matrix);
}
}
}
}

0 comments on commit 2320f16

Please sign in to comment.