Browse Source

Merge branch 'main' into bp/fixIssue3078

pull/3080/head
James Jackson-South 2 months ago
committed by GitHub
parent
commit
5842434b7d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      README.md
  2. 12
      src/ImageSharp/Primitives/Point.cs
  3. 12
      src/ImageSharp/Primitives/PointF.cs
  4. 14
      src/ImageSharp/Primitives/Rectangle.cs
  5. 14
      src/ImageSharp/Primitives/RectangleF.cs
  6. 10
      src/ImageSharp/Processing/AffineTransformBuilder.cs
  7. 16
      src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs
  8. 10
      src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
  9. 63
      tests/ImageSharp.Tests/Primitives/PointFTests.cs
  10. 45
      tests/ImageSharp.Tests/Primitives/PointTests.cs
  11. 43
      tests/ImageSharp.Tests/Primitives/RectangleFTests.cs
  12. 33
      tests/ImageSharp.Tests/Primitives/RectangleTests.cs
  13. 3
      tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs
  14. 3
      tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs
  15. 44
      tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs

8
README.md

@ -10,16 +10,14 @@ SixLabors.ImageSharp
[![Build Status](https://img.shields.io/github/actions/workflow/status/SixLabors/ImageSharp/build-and-test.yml?branch=main)](https://github.com/SixLabors/ImageSharp/actions) [![Build Status](https://img.shields.io/github/actions/workflow/status/SixLabors/ImageSharp/build-and-test.yml?branch=main)](https://github.com/SixLabors/ImageSharp/actions)
[![codecov](https://codecov.io/gh/SixLabors/ImageSharp/graph/badge.svg?token=g2WJwz770q)](https://codecov.io/gh/SixLabors/ImageSharp) [![codecov](https://codecov.io/gh/SixLabors/ImageSharp/graph/badge.svg?token=g2WJwz770q)](https://codecov.io/gh/SixLabors/ImageSharp)
[![License: Six Labors Split](https://img.shields.io/badge/license-Six%20Labors%20Split-%23e30183)](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE) [![License: Six Labors Split](https://img.shields.io/badge/license-Six%20Labors%20Split-%23e30183)](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE)
[![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=flat&logo=twitter)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors)
</div> </div>
### **ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API. ### **ImageSharp** is a high-performance, fully managed, cross-platform 2D graphics API.
ImageSharp is a new, fully featured, fully managed, cross-platform, 2D graphics library. ImageSharp is a mature, fully featured, high-performance image processing and graphics library for .NET, built for workloads across device, cloud, and embedded/IoT scenarios.
Designed to simplify image processing, ImageSharp brings you an incredibly powerful yet beautifully simple API.
ImageSharp is designed from the ground up to be flexible and extensible. The library provides API endpoints for common image processing operations and the building blocks to allow for the development of additional operations. Designed from the ground up to balance performance, portability, and ease of use, ImageSharp provides a powerful yet approachable API for common image processing tasks, along with the low-level building blocks needed to extend the library for specialized workflows.
Built against [.NET 8](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios. Built against [.NET 8](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios.

12
src/ImageSharp/Primitives/Point.cs

@ -4,6 +4,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp; namespace SixLabors.ImageSharp;
@ -234,6 +235,17 @@ public struct Point : IEquatable<Point>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix)); public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix));
/// <summary>
/// Transforms a point by a specified 4x4 matrix, applying a projective transform
/// flattened into 2D space.
/// </summary>
/// <param name="point">The point to transform.</param>
/// <param name="matrix">The transformation matrix used.</param>
/// <returns>The transformed <see cref="Point"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Transform(Point point, Matrix4x4 matrix)
=> Round(TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix));
/// <summary> /// <summary>
/// Deconstructs this point into two integers. /// Deconstructs this point into two integers.
/// </summary> /// </summary>

12
src/ImageSharp/Primitives/PointF.cs

@ -4,6 +4,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp; namespace SixLabors.ImageSharp;
@ -246,6 +247,17 @@ public struct PointF : IEquatable<PointF>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Transform(PointF point, Matrix3x2 matrix) => Vector2.Transform(point, matrix); public static PointF Transform(PointF point, Matrix3x2 matrix) => Vector2.Transform(point, matrix);
/// <summary>
/// Transforms a point by a specified 4x4 matrix, applying a projective transform
/// flattened into 2D space.
/// </summary>
/// <param name="point">The point to transform.</param>
/// <param name="matrix">The transformation matrix used.</param>
/// <returns>The transformed <see cref="PointF"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Transform(PointF point, Matrix4x4 matrix)
=> TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix);
/// <summary> /// <summary>
/// Deconstructs this point into two floats. /// Deconstructs this point into two floats.
/// </summary> /// </summary>

14
src/ImageSharp/Primitives/Rectangle.cs

@ -266,6 +266,20 @@ public struct Rectangle : IEquatable<Rectangle>
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
} }
/// <summary>
/// Transforms a rectangle by the given 4x4 matrix, applying a projective transform
/// flattened into 2D space.
/// </summary>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>A transformed rectangle.</returns>
public static RectangleF Transform(Rectangle rectangle, Matrix4x4 matrix)
{
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
PointF topLeft = PointF.Transform(new PointF(rectangle.Location.X, rectangle.Location.Y), matrix);
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
}
/// <summary> /// <summary>
/// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a truncate operation on all the coordinates. /// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a truncate operation on all the coordinates.
/// </summary> /// </summary>

14
src/ImageSharp/Primitives/RectangleF.cs

@ -241,6 +241,20 @@ public struct RectangleF : IEquatable<RectangleF>
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
} }
/// <summary>
/// Transforms a rectangle by the given 4x4 matrix, applying a projective transform
/// flattened into 2D space.
/// </summary>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>A transformed <see cref="RectangleF"/>.</returns>
public static RectangleF Transform(RectangleF rectangle, Matrix4x4 matrix)
{
PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
PointF topLeft = PointF.Transform(rectangle.Location, matrix);
return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
}
/// <summary> /// <summary>
/// Creates a rectangle that represents the union between <paramref name="a"/> and <paramref name="b"/>. /// Creates a rectangle that represents the union between <paramref name="a"/> and <paramref name="b"/>.
/// </summary> /// </summary>

10
src/ImageSharp/Processing/AffineTransformBuilder.cs

@ -349,6 +349,16 @@ public class AffineTransformBuilder
internal static SizeF GetTransformedSize(Rectangle sourceRectangle, Matrix3x2 matrix) internal static SizeF GetTransformedSize(Rectangle sourceRectangle, Matrix3x2 matrix)
=> TransformUtilities.GetRawTransformedSize(matrix, sourceRectangle.Size); => TransformUtilities.GetRawTransformedSize(matrix, sourceRectangle.Size);
/// <summary>
/// Clears all accumulated transform matrices, resetting the builder to its initial state.
/// </summary>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder Clear()
{
this.transformMatrixFactories.Clear();
return this;
}
private static void CheckDegenerate(Matrix3x2 matrix) private static void CheckDegenerate(Matrix3x2 matrix)
{ {
if (TransformUtilities.IsDegenerate(matrix)) if (TransformUtilities.IsDegenerate(matrix))

16
src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs

@ -69,11 +69,17 @@ internal static class TransformUtilities
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix)
{ {
// The w component (v4.W) resulting from the transformation can be less than 0 in certain cases, // Transforms the 2D point (x, y) as the homogeneous coordinate (x, y, 0, 1) and
// such as when the point is transformed behind the camera in a perspective projection. // performs the perspective divide (X/W, Y/W) to project back into Cartesian 2D space.
// However, in many 2D contexts, negative w values are not meaningful and could cause issues //
// like flipped or distorted projections. To avoid this, we take the max of w and epsilon to ensure // For affine matrices (M14=0, M24=0, M34=0, M44=1) W is always 1 and the divide
// we don't divide by a very small or negative number, effectively treating any negative w as epsilon. // is a no-op, producing the same result as Vector2.Transform(v, Matrix4x4).AsVector2()
// (the approach used by .NET 10+).
//
// For projective matrices (taper, quad distortion) W varies per point and the divide
// is essential for correct perspective mapping. W <= 0 means the point has crossed the
// vanishing line of the projection; clamping to epsilon avoids division by zero or
// negative values that would flip/mirror the output.
const float epsilon = 0.0000001F; const float epsilon = 0.0000001F;
Vector4 v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); Vector4 v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix);
return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, epsilon); return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, epsilon);

10
src/ImageSharp/Processing/ProjectiveTransformBuilder.cs

@ -397,6 +397,16 @@ public class ProjectiveTransformBuilder
internal static SizeF GetTransformedSize(Rectangle sourceRectangle, Matrix4x4 matrix) internal static SizeF GetTransformedSize(Rectangle sourceRectangle, Matrix4x4 matrix)
=> TransformUtilities.GetRawTransformedSize(matrix, sourceRectangle.Size); => TransformUtilities.GetRawTransformedSize(matrix, sourceRectangle.Size);
/// <summary>
/// Clears all accumulated transform matrices, resetting the builder to its initial state.
/// </summary>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder Clear()
{
this.transformMatrixFactories.Clear();
return this;
}
private static void CheckDegenerate(Matrix4x4 matrix) private static void CheckDegenerate(Matrix4x4 matrix)
{ {
if (TransformUtilities.IsDegenerate(matrix)) if (TransformUtilities.IsDegenerate(matrix))

63
tests/ImageSharp.Tests/Primitives/PointFTests.cs

@ -133,6 +133,69 @@ public class PointFTests
Assert.Equal(new PointF(30, 30), pout); Assert.Equal(new PointF(30, 30), pout);
} }
[Fact]
public void TransformMatrix4x4_AffineMatchesMatrix3x2()
{
PointF p = new(13, 17);
Matrix3x2 m3 = Matrix3x2Extensions.CreateRotationDegrees(45, PointF.Empty);
Matrix4x4 m4 = new(m3);
PointF r3 = PointF.Transform(p, m3);
PointF r4 = PointF.Transform(p, m4);
Assert.Equal(r3.X, r4.X, ApproximateFloatComparer);
Assert.Equal(r3.Y, r4.Y, ApproximateFloatComparer);
}
[Fact]
public void TransformMatrix4x4_Identity()
{
PointF p = new(42.5F, -17.3F);
PointF result = PointF.Transform(p, Matrix4x4.Identity);
Assert.Equal(p.X, result.X, ApproximateFloatComparer);
Assert.Equal(p.Y, result.Y, ApproximateFloatComparer);
}
[Fact]
public void TransformMatrix4x4_Translation()
{
PointF p = new(10, 20);
Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0);
PointF result = PointF.Transform(p, m);
Assert.Equal(15F, result.X, ApproximateFloatComparer);
Assert.Equal(17F, result.Y, ApproximateFloatComparer);
}
[Fact]
public void TransformMatrix4x4_Scale()
{
PointF p = new(10, 20);
Matrix4x4 m = Matrix4x4.CreateScale(2, 3, 1);
PointF result = PointF.Transform(p, m);
Assert.Equal(20F, result.X, ApproximateFloatComparer);
Assert.Equal(60F, result.Y, ApproximateFloatComparer);
}
[Fact]
public void TransformMatrix4x4_Projective()
{
// A taper matrix with M14 != 0 produces W != 1, requiring perspective divide.
PointF p = new(100, 50);
Matrix4x4 m = Matrix4x4.Identity;
m.M14 = 0.005F; // perspective component
PointF result = PointF.Transform(p, m);
// W = x*M14 + M44 = 100*0.005 + 1 = 1.5
// X = x*M11 + M41 = 100, Y = y*M22 + M42 = 50
// result = (100/1.5, 50/1.5)
Assert.Equal(100F / 1.5F, result.X, ApproximateFloatComparer);
Assert.Equal(50F / 1.5F, result.Y, ApproximateFloatComparer);
}
[Theory] [Theory]
[InlineData(float.MaxValue, float.MinValue)] [InlineData(float.MaxValue, float.MinValue)]
[InlineData(float.MinValue, float.MaxValue)] [InlineData(float.MinValue, float.MaxValue)]

45
tests/ImageSharp.Tests/Primitives/PointTests.cs

@ -174,6 +174,51 @@ public class PointTests
Assert.Equal(new Point(30, 30), pout); Assert.Equal(new Point(30, 30), pout);
} }
[Fact]
public void TransformMatrix4x4_AffineMatchesMatrix3x2()
{
Point p = new(13, 17);
Matrix3x2 m3 = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty);
Matrix4x4 m4 = new(m3);
Point r3 = Point.Transform(p, m3);
Point r4 = Point.Transform(p, m4);
Assert.Equal(r3, r4);
}
[Fact]
public void TransformMatrix4x4_Identity()
{
Point p = new(42, -17);
Point result = Point.Transform(p, Matrix4x4.Identity);
Assert.Equal(p, result);
}
[Fact]
public void TransformMatrix4x4_Translation()
{
Point p = new(10, 20);
Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0);
Point result = Point.Transform(p, m);
Assert.Equal(new Point(15, 17), result);
}
[Fact]
public void TransformMatrix4x4_Projective()
{
Point p = new(100, 50);
Matrix4x4 m = Matrix4x4.Identity;
m.M14 = 0.005F;
Point result = Point.Transform(p, m);
// W = 100*0.005 + 1 = 1.5 => (100/1.5, 50/1.5) => rounded
Assert.Equal(Point.Round(new PointF(100F / 1.5F, 50F / 1.5F)), result);
}
[Theory] [Theory]
[InlineData(int.MaxValue, int.MinValue)] [InlineData(int.MaxValue, int.MinValue)]
[InlineData(int.MinValue, int.MinValue)] [InlineData(int.MinValue, int.MinValue)]

43
tests/ImageSharp.Tests/Primitives/RectangleFTests.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Globalization; using System.Globalization;
using System.Numerics;
namespace SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Tests;
@ -243,6 +244,48 @@ public class RectangleFTests
Assert.Equal(expectedRect, r1); Assert.Equal(expectedRect, r1);
} }
[Fact]
public void TransformMatrix4x4_AffineMatchesMatrix3x2()
{
RectangleF rect = new(10, 20, 100, 50);
Matrix3x2 m3 = Matrix3x2.CreateTranslation(5, -3);
Matrix4x4 m4 = new(m3);
RectangleF r3 = RectangleF.Transform(rect, m3);
RectangleF r4 = RectangleF.Transform(rect, m4);
Assert.Equal(r3, r4);
}
[Fact]
public void TransformMatrix4x4_Identity()
{
RectangleF rect = new(10, 20, 100, 50);
RectangleF result = RectangleF.Transform(rect, Matrix4x4.Identity);
Assert.Equal(rect, result);
}
[Fact]
public void TransformMatrix4x4_Translation()
{
RectangleF rect = new(10, 20, 100, 50);
Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0);
RectangleF result = RectangleF.Transform(rect, m);
Assert.Equal(new RectangleF(15, 17, 100, 50), result);
}
[Fact]
public void TransformMatrix4x4_Scale()
{
RectangleF rect = new(10, 20, 100, 50);
Matrix4x4 m = Matrix4x4.CreateScale(2, 3, 1);
RectangleF result = RectangleF.Transform(rect, m);
Assert.Equal(new RectangleF(20, 60, 200, 150), result);
}
[Fact] [Fact]
public void ToStringTest() public void ToStringTest()
{ {

33
tests/ImageSharp.Tests/Primitives/RectangleTests.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Globalization; using System.Globalization;
using System.Numerics;
namespace SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Tests;
@ -294,6 +295,38 @@ public class RectangleTests
Assert.Equal(expectedRect, r1); Assert.Equal(expectedRect, r1);
} }
[Fact]
public void TransformMatrix4x4_AffineMatchesMatrix3x2()
{
Rectangle rect = new(10, 20, 100, 50);
Matrix3x2 m3 = Matrix3x2.CreateTranslation(5, -3);
Matrix4x4 m4 = new(m3);
RectangleF r3 = Rectangle.Transform(rect, m3);
RectangleF r4 = Rectangle.Transform(rect, m4);
Assert.Equal(r3, r4);
}
[Fact]
public void TransformMatrix4x4_Identity()
{
Rectangle rect = new(10, 20, 100, 50);
RectangleF result = Rectangle.Transform(rect, Matrix4x4.Identity);
Assert.Equal(new RectangleF(10, 20, 100, 50), result);
}
[Fact]
public void TransformMatrix4x4_Translation()
{
Rectangle rect = new(10, 20, 100, 50);
Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0);
RectangleF result = Rectangle.Transform(rect, m);
Assert.Equal(new RectangleF(15, 17, 100, 50), result);
}
[Fact] [Fact]
public void ToStringTest() public void ToStringTest()
{ {

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

@ -58,6 +58,9 @@ public class AffineTransformBuilderTests : TransformBuilderTestBase<AffineTransf
protected override void PrependTranslation(AffineTransformBuilder builder, PointF translate) protected override void PrependTranslation(AffineTransformBuilder builder, PointF translate)
=> builder.PrependTranslation(translate); => builder.PrependTranslation(translate);
protected override void ClearBuilder(AffineTransformBuilder builder)
=> builder.Clear();
protected override Vector2 Execute( protected override Vector2 Execute(
AffineTransformBuilder builder, AffineTransformBuilder builder,
Rectangle rectangle, Rectangle rectangle,

3
tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs

@ -55,6 +55,9 @@ public class ProjectiveTransformBuilderTests : TransformBuilderTestBase<Projecti
protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) => protected override void PrependRotationRadians(ProjectiveTransformBuilder builder, float radians, Vector2 origin) =>
builder.PrependRotationRadians(radians, origin); builder.PrependRotationRadians(radians, origin);
protected override void ClearBuilder(ProjectiveTransformBuilder builder)
=> builder.Clear();
protected override Vector2 Execute( protected override Vector2 Execute(
ProjectiveTransformBuilder builder, ProjectiveTransformBuilder builder,
Rectangle rectangle, Rectangle rectangle,

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

@ -248,6 +248,48 @@ public abstract class TransformBuilderTestBase<TBuilder>
}); });
} }
[Fact]
public void Clear_ResetsBuilderToIdentity()
{
Size size = new(100, 100);
Rectangle rectangle = new(Point.Empty, size);
Vector2 source = new(10, 20);
TBuilder builder = this.CreateBuilder();
// Apply a transform that changes the point.
this.AppendScale(builder, new SizeF(2, 3));
this.AppendTranslation(builder, new PointF(50, 50));
Vector2 transformed = this.Execute(builder, rectangle, source);
Assert.NotEqual(source, transformed, Comparer);
// Clear and verify the builder produces identity behavior.
this.ClearBuilder(builder);
Vector2 afterClear = this.Execute(builder, rectangle, source);
Assert.Equal(source, afterClear, Comparer);
}
[Fact]
public void Clear_AllowsReuse()
{
Size size = new(100, 100);
Rectangle rectangle = new(Point.Empty, size);
Vector2 source = new(10, 20);
TBuilder builder = this.CreateBuilder();
// First transform: scale by 2.
this.AppendScale(builder, new SizeF(2, 2));
Vector2 scaled = this.Execute(builder, rectangle, source);
Assert.Equal(new Vector2(20, 40), scaled, Comparer);
// Clear and apply a different transform: translate.
this.ClearBuilder(builder);
this.AppendTranslation(builder, new PointF(5, 10));
Vector2 translated = this.Execute(builder, rectangle, source);
Assert.Equal(new Vector2(15, 30), translated, Comparer);
}
protected abstract TBuilder CreateBuilder(); protected abstract TBuilder CreateBuilder();
protected abstract void AppendRotationDegrees(TBuilder builder, float degrees); protected abstract void AppendRotationDegrees(TBuilder builder, float degrees);
@ -282,5 +324,7 @@ public abstract class TransformBuilderTestBase<TBuilder>
protected abstract void PrependTranslation(TBuilder builder, PointF translate); protected abstract void PrependTranslation(TBuilder builder, PointF translate);
protected abstract void ClearBuilder(TBuilder builder);
protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint); protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint);
} }

Loading…
Cancel
Save