Browse Source

add more tests

pull/775/head
Anton Firszov 7 years ago
parent
commit
da50180e1c
  1. 15
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  2. 35
      src/ImageSharp/Processing/AffineTransformBuilder.cs
  3. 11
      src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
  4. 67
      tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs
  5. 151
      tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs
  6. 13
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

15
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -22,7 +22,8 @@ namespace SixLabors.ImageSharp
/// <param name="b">The blue component.</param>
/// <returns>The <see cref="byte"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static byte Get8BitBT709Luminance(byte r, byte g, byte b) => (byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5f);
public static byte Get8BitBT709Luminance(byte r, byte g, byte b) =>
(byte)((r * .2126F) + (g * .7152F) + (b * .0722F) + 0.5f);
/// <summary>
/// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
@ -32,7 +33,8 @@ namespace SixLabors.ImageSharp
/// <param name="b">The blue component.</param>
/// <returns>The <see cref="ushort"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) => (ushort)((r * .2126F) + (g * .7152F) + (b * .0722F));
public static ushort Get16BitBT709Luminance(ushort r, ushort g, ushort b) =>
(ushort)((r * .2126F) + (g * .7152F) + (b * .0722F));
/// <summary>
/// Scales a value from a 16 bit <see cref="ushort"/> to it's 8 bit <see cref="byte"/> equivalent.
@ -128,6 +130,15 @@ namespace SixLabors.ImageSharp
return x & (m - 1);
}
/// <summary>
/// Converts degrees to radians
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public static float ToRadian(float degrees)
{
return degrees * ((float)Math.PI / 180f);
}
/// <summary>
/// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation.
/// </summary>

35
src/ImageSharp/Processing/AffineTransformBuilder.cs

@ -14,18 +14,15 @@ namespace SixLabors.ImageSharp.Processing
public class AffineTransformBuilder
{
private readonly List<Matrix3x2> matrices = new List<Matrix3x2>();
private Rectangle rectangle;
private readonly Rectangle rectangle;
/// <summary>
/// Initializes a new instance of the <see cref="AffineTransformBuilder"/> class.
/// </summary>
/// <param name="sourceSize">The source image size.</param>
public AffineTransformBuilder(Size sourceSize)
: this(new Rectangle(Point.Empty, sourceSize))
{
Guard.MustBeGreaterThan(sourceSize.Width, 0, nameof(sourceSize));
Guard.MustBeGreaterThan(sourceSize.Height, 0, nameof(sourceSize));
this.Size = sourceSize;
}
/// <summary>
@ -33,13 +30,17 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
/// <param name="sourceRectangle">The source rectangle.</param>
public AffineTransformBuilder(Rectangle sourceRectangle)
: this(sourceRectangle.Size)
=> this.rectangle = sourceRectangle;
{
Guard.MustBeGreaterThan(sourceRectangle.Width, 0, nameof(sourceRectangle));
Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle));
this.rectangle = sourceRectangle;
}
/// <summary>
/// Gets the source image size.
/// </summary>
internal Size Size { get; }
internal Size Size => this.rectangle.Size;
/// <summary>
/// Prepends a centered rotation matrix using the given rotation in degrees.
@ -49,13 +50,29 @@ namespace SixLabors.ImageSharp.Processing
public AffineTransformBuilder PrependRotateMatrixDegrees(float degrees)
=> this.PrependMatrix(TransformUtils.CreateRotationMatrixDegrees(degrees, this.Size));
/// <summary>
/// Prepends a centered rotation matrix using the given rotation in radians.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependRotateMatrixRadians(float radians)
=> this.PrependMatrix(TransformUtils.CreateRotationMatrixRadians(radians, this.Size));
/// <summary>
/// Appends a centered rotation matrix using the given rotation in degrees.
/// </summary>
/// <param name="degrees">The amount of rotation, in degrees.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendRotateMatrixDegrees(float degrees)
=> this.AppendMatrix(TransformUtils.CreateRotationMatrixDegrees(degrees, this.Size));
=> this.AppendRotateMatrixRadians(ImageMaths.ToRadian(degrees));
/// <summary>
/// Appends a centered rotation matrix using the given rotation in radians.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendRotateMatrixRadians(float radians)
=> this.AppendMatrix(TransformUtils.CreateRotationMatrixRadians(radians, this.Size));
/// <summary>
/// Prepends a scale matrix from the given vector scale.

11
src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs

@ -23,6 +23,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty));
/// <summary>
/// Creates a centered rotation matrix using the given rotation in radians and the source size.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size)
=> CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateRotation(radians, PointF.Empty));
/// <summary>
/// Creates a centered skew matrix from the give angles in degrees and the source size.
/// </summary>

67
tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs

@ -4,12 +4,13 @@
using System;
using System.Numerics;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{
public class AffineTransformBuilderTests
public class AffineTransformBuilderTests : TransformBuilderTestBase<AffineTransformBuilder>
{
[Fact]
public void ConstructorAssignsProperties()
@ -23,57 +24,33 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public void ConstructorThrowsInvalid()
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
var s = new Size(0, 1);
var builder = new AffineTransformBuilder(new Rectangle(Point.Empty, s));
});
{
var s = new Size(0, 1);
var builder = new AffineTransformBuilder(new Rectangle(Point.Empty, s));
});
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
var s = new Size(1, 0);
var builder = new AffineTransformBuilder(new Rectangle(Point.Empty, s));
});
{
var s = new Size(1, 0);
var builder = new AffineTransformBuilder(new Rectangle(Point.Empty, s));
});
}
[Fact]
public void AppendPrependOpposite()
{
var rectangle = new Rectangle(-1, -1, 3, 3);
var b1 = new AffineTransformBuilder(rectangle);
var b2 = new AffineTransformBuilder(rectangle);
const float pi = (float)Math.PI;
// Forwards
b1.AppendRotateMatrixDegrees(pi)
.AppendSkewMatrixDegrees(pi, pi)
.AppendScaleMatrix(new SizeF(pi, pi))
.AppendTranslationMatrix(new PointF(pi, pi));
// Backwards
b2.PrependTranslationMatrix(new PointF(pi, pi))
.PrependScaleMatrix(new SizeF(pi, pi))
.PrependSkewMatrixDegrees(pi, pi)
.PrependRotateMatrixDegrees(pi);
protected override void AppendTranslation(AffineTransformBuilder builder, PointF translate) => builder.AppendTranslationMatrix(translate);
protected override void AppendScale(AffineTransformBuilder builder, SizeF scale) => builder.AppendScaleMatrix(scale);
protected override void AppendRotationRadians(AffineTransformBuilder builder, float radians) => builder.AppendRotateMatrixRadians(radians);
Assert.Equal(b1.BuildMatrix(), b2.BuildMatrix());
}
protected override void PrependTranslation(AffineTransformBuilder builder, PointF translate) => builder.PrependTranslationMatrix(translate);
protected override void PrependScale(AffineTransformBuilder builder, SizeF scale) => builder.PrependScaleMatrix(scale);
protected override void PrependRotationRadians(AffineTransformBuilder builder, float radians) => builder.PrependRotateMatrixRadians(radians);
[Fact]
public void BuilderCanClear()
protected override Vector2 Execute(
AffineTransformBuilder builder,
Rectangle rectangle,
Vector2 sourcePoint)
{
var rectangle = new Rectangle(0, 0, 3, 3);
var builder = new AffineTransformBuilder(rectangle);
Matrix3x2 matrix = Matrix3x2.Identity;
matrix.M31 = (float)Math.PI;
Assert.Equal(Matrix3x2.Identity, builder.BuildMatrix());
builder.AppendMatrix(matrix);
Assert.NotEqual(Matrix3x2.Identity, builder.BuildMatrix());
builder.Clear();
Assert.Equal(Matrix3x2.Identity, builder.BuildMatrix());
Matrix3x2 matrix = builder.BuildMatrix();
return Vector2.Transform(sourcePoint, matrix);
}
}
}

151
tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs

@ -0,0 +1,151 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{
public abstract class TransformBuilderTestBase<TBuilder>
{
private static readonly ApproximateFloatComparer Comparer = new ApproximateFloatComparer(1e-6f);
public static readonly TheoryData<Vector2, Vector2, Vector2, Vector2> ScaleTranslate_Data =
new TheoryData<Vector2, Vector2, Vector2, Vector2>
{
// scale, translate, source, expectedDest
{ Vector2.One, Vector2.Zero, Vector2.Zero, Vector2.Zero },
{ Vector2.One, Vector2.Zero, new Vector2(10, 20), new Vector2(10, 20) },
{ Vector2.One, new Vector2(3, 1), new Vector2(10, 20), new Vector2(13, 21) },
{ new Vector2(2, 0.5f), new Vector2(3, 1), new Vector2(10, 20), new Vector2(23, 11) },
};
[Theory]
[MemberData(nameof(ScaleTranslate_Data))]
public void _1Scale_2Translate(Vector2 scale, Vector2 translate, Vector2 source, Vector2 expectedDest)
{
// These operations should be size-agnostic:
var size = new Size(123, 321);
TBuilder builder = this.CreateBuilder(size);
this.AppendScale(builder, new SizeF(scale));
this.AppendTranslation(builder, translate);
Vector2 actualDest = this.Execute(builder, new Rectangle(Point.Empty, size), source);
Assert.True(Comparer.Equals(expectedDest, actualDest));
}
public static readonly TheoryData<Vector2, Vector2, Vector2, Vector2> TranslateScale_Data =
new TheoryData<Vector2, Vector2, Vector2, Vector2>
{
// translate, scale, source, expectedDest
{ Vector2.Zero, Vector2.One, Vector2.Zero, Vector2.Zero },
{ Vector2.Zero, Vector2.One, new Vector2(10, 20), new Vector2(10, 20) },
{ new Vector2(3, 1), new Vector2(2, 0.5f), new Vector2(10, 20), new Vector2(26, 10.5f) },
};
[Theory]
[MemberData(nameof(TranslateScale_Data))]
public void _1Translate_2Scale(Vector2 translate, Vector2 scale, Vector2 source, Vector2 expectedDest)
{
// Translate ans scale are size-agnostic:
var size = new Size(456, 432);
TBuilder builder = this.CreateBuilder(size);
this.AppendTranslation(builder, translate);
this.AppendScale(builder, new SizeF(scale));
Vector2 actualDest = this.Execute(builder, new Rectangle(Point.Empty, size), source);
Assert.Equal(expectedDest, actualDest, Comparer);
}
[Theory]
[InlineData(10, 20)]
[InlineData(-20, 10)]
public void LocationOffsetIsPrepended(int locationX, int locationY)
{
var rectangle = new Rectangle(locationX, locationY, 10, 10);
TBuilder builder = this.CreateBuilder(rectangle);
this.AppendScale(builder, new SizeF(2, 2));
Vector2 actual = this.Execute(builder, rectangle, Vector2.One);
Vector2 expected = new Vector2(-locationX + 1, -locationY + 1) * 2;
Assert.Equal(actual, expected, Comparer);
}
[Theory]
[InlineData(200, 100, 10, 42, 84)]
[InlineData(200, 100, 100, 42, 84)]
[InlineData(100, 200, -10, 42, 84)]
public void RotateDegrees_ShouldCreateCenteredMatrix(int width, int height, float deg, float x, float y)
{
var size = new Size(width, height);
TBuilder builder = this.CreateBuilder(size);
this.AppendRotationDegrees(builder, deg);
// TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness
Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(deg, size);
var position = new Vector2(x, y);
var expected = Vector2.Transform(position, matrix);
Vector2 actual = this.Execute(builder, new Rectangle(Point.Empty, size), position);
Assert.Equal(actual, expected, Comparer);
}
[Fact]
public void AppendPrependOpposite()
{
var rectangle = new Rectangle(-1, -1, 3, 3);
TBuilder b1 = this.CreateBuilder(rectangle);
TBuilder b2 = this.CreateBuilder(rectangle);
const float pi = (float)Math.PI;
// Forwards
this.AppendRotationRadians(b1, pi);
this.AppendScale(b1, new SizeF(2, 0.5f));
this.AppendTranslation(b1, new PointF(123, 321));
// Backwards
this.PrependTranslation(b2, new PointF(123, 321));
this.PrependScale(b2, new SizeF(2, 0.5f));
this.PrependRotationRadians(b2, pi);
Vector2 p1 = this.Execute(b1, rectangle, new Vector2(32, 65));
Vector2 p2 = this.Execute(b2, rectangle, new Vector2(32, 65));
Assert.Equal(p1, p2, Comparer);
}
protected TBuilder CreateBuilder(Size size) => this.CreateBuilder(new Rectangle(Point.Empty, size));
protected virtual TBuilder CreateBuilder(Rectangle rectangle) => (TBuilder)Activator.CreateInstance(typeof(TBuilder), rectangle);
protected abstract void AppendTranslation(TBuilder builder, PointF translate);
protected abstract void AppendScale(TBuilder builder, SizeF scale);
protected abstract void AppendRotationRadians(TBuilder builder, float radians);
protected abstract void PrependTranslation(TBuilder builder, PointF translate);
protected abstract void PrependScale(TBuilder builder, SizeF scale);
protected abstract void PrependRotationRadians(TBuilder builder, float radians);
protected virtual void AppendRotationDegrees(TBuilder builder, float degrees) =>
this.AppendRotationRadians(builder, ImageMaths.ToRadian(degrees));
protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint);
private static float Sqrt(float a) => (float)Math.Sqrt(a);
}
}

13
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -11,7 +11,8 @@ namespace SixLabors.ImageSharp.Tests
/// </summary>
internal readonly struct ApproximateFloatComparer :
IEqualityComparer<float>,
IEqualityComparer<Vector4>
IEqualityComparer<Vector4>,
IEqualityComparer<Vector2>
{
private readonly float Epsilon;
@ -33,9 +34,17 @@ namespace SixLabors.ImageSharp.Tests
public int GetHashCode(float obj) => obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W);
public bool Equals(Vector4 a, Vector4 b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z) && this.Equals(a.W, b.W);
/// <inheritdoc/>
public int GetHashCode(Vector4 obj) => obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector2 a, Vector2 b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y);
public int GetHashCode(Vector2 obj)
{
throw new System.NotImplementedException();
}
}
}
Loading…
Cancel
Save