-
-
Notifications
You must be signed in to change notification settings - Fork 893
Expand file tree
/
Copy pathTransformBuilderTestBase.cs
More file actions
288 lines (227 loc) · 11.5 KB
/
TransformBuilderTestBase.cs
File metadata and controls
288 lines (227 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
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))]
#pragma warning disable SA1300 // Element should begin with upper-case letter
public void _1Scale_2Translate(Vector2 scale, Vector2 translate, Vector2 source, Vector2 expectedDest)
#pragma warning restore SA1300 // Element should begin with upper-case letter
{
// These operations should be size-agnostic:
var size = new Size(123, 321);
TBuilder builder = this.CreateBuilder();
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))]
#pragma warning disable SA1300 // Element should begin with upper-case letter
public void _1Translate_2Scale(Vector2 translate, Vector2 scale, Vector2 source, Vector2 expectedDest)
#pragma warning restore SA1300 // Element should begin with upper-case letter
{
// Translate ans scale are size-agnostic:
var size = new Size(456, 432);
TBuilder builder = this.CreateBuilder();
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();
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 AppendRotationDegrees_WithoutSpecificRotationCenter_RotationIsCenteredAroundImageCenter(
int width,
int height,
float degrees,
float x,
float y)
{
var size = new Size(width, height);
TBuilder builder = this.CreateBuilder();
this.AppendRotationDegrees(builder, degrees);
// TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness
Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, 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);
}
[Theory]
[InlineData(200, 100, 10, 30, 61, 42, 84)]
[InlineData(200, 100, 100, 30, 10, 20, 84)]
[InlineData(100, 200, -10, 30, 20, 11, 84)]
public void AppendRotationDegrees_WithRotationCenter(
int width,
int height,
float degrees,
float cx,
float cy,
float x,
float y)
{
var size = new Size(width, height);
TBuilder builder = this.CreateBuilder();
var centerPoint = new Vector2(cx, cy);
this.AppendRotationDegrees(builder, degrees, centerPoint);
var matrix = Matrix3x2.CreateRotation(GeometryUtilities.DegreeToRadian(degrees), centerPoint);
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);
}
[Theory]
[InlineData(200, 100, 10, 10, 42, 84)]
[InlineData(200, 100, 100, 100, 42, 84)]
[InlineData(100, 200, -10, -10, 42, 84)]
public void AppendSkewDegrees_WithoutSpecificSkewCenter_SkewIsCenteredAroundImageCenter(
int width,
int height,
float degreesX,
float degreesY,
float x,
float y)
{
var size = new Size(width, height);
TBuilder builder = this.CreateBuilder();
this.AppendSkewDegrees(builder, degreesX, degreesY);
Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, 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);
}
[Theory]
[InlineData(200, 100, 10, 10, 30, 61, 42, 84)]
[InlineData(200, 100, 100, 100, 30, 10, 20, 84)]
[InlineData(100, 200, -10, -10, 30, 20, 11, 84)]
public void AppendSkewDegrees_WithSkewCenter(
int width,
int height,
float degreesX,
float degreesY,
float cx,
float cy,
float x,
float y)
{
var size = new Size(width, height);
TBuilder builder = this.CreateBuilder();
var centerPoint = new Vector2(cx, cy);
this.AppendSkewDegrees(builder, degreesX, degreesY, centerPoint);
var matrix = Matrix3x2.CreateSkew(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), centerPoint);
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();
TBuilder b2 = this.CreateBuilder();
const float pi = (float)Math.PI;
// Forwards
this.AppendRotationRadians(b1, pi);
this.AppendSkewRadians(b1, pi, pi);
this.AppendScale(b1, new SizeF(2, 0.5f));
this.AppendRotationRadians(b1, pi / 2, new Vector2(-0.5f, -0.1f));
this.AppendSkewRadians(b1, pi, pi / 2, new Vector2(-0.5f, -0.1f));
this.AppendTranslation(b1, new PointF(123, 321));
// Backwards
this.PrependTranslation(b2, new PointF(123, 321));
this.PrependSkewRadians(b2, pi, pi / 2, new Vector2(-0.5f, -0.1f));
this.PrependRotationRadians(b2, pi / 2, new Vector2(-0.5f, -0.1f));
this.PrependScale(b2, new SizeF(2, 0.5f));
this.PrependSkewRadians(b2, pi, pi);
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);
}
[Theory]
[InlineData(0, 1)]
[InlineData(1, 0)]
[InlineData(-1, 0)]
public void ThrowsForInvalidSizes(int width, int height)
{
var size = new Size(width, height);
Assert.ThrowsAny<ArgumentOutOfRangeException>(
() =>
{
TBuilder builder = this.CreateBuilder();
this.Execute(builder, new Rectangle(Point.Empty, size), Vector2.Zero);
});
}
[Fact]
public void ThrowsForInvalidMatrix()
{
Assert.ThrowsAny<DegenerateTransformException>(
() =>
{
TBuilder builder = this.CreateBuilder();
this.AppendSkewDegrees(builder, 45, 45);
this.Execute(builder, new Rectangle(0, 0, 150, 150), Vector2.Zero);
});
}
protected abstract TBuilder CreateBuilder();
protected abstract void AppendRotationDegrees(TBuilder builder, float degrees);
protected abstract void AppendRotationDegrees(TBuilder builder, float degrees, Vector2 origin);
protected abstract void AppendRotationRadians(TBuilder builder, float radians);
protected abstract void AppendRotationRadians(TBuilder builder, float radians, Vector2 origin);
protected abstract void AppendScale(TBuilder builder, SizeF scale);
protected abstract void AppendSkewDegrees(TBuilder builder, float degreesX, float degreesY);
protected abstract void AppendSkewDegrees(TBuilder builder, float degreesX, float degreesY, Vector2 origin);
protected abstract void AppendSkewRadians(TBuilder builder, float radiansX, float radiansY);
protected abstract void AppendSkewRadians(TBuilder builder, float radiansX, float radiansY, Vector2 origin);
protected abstract void AppendTranslation(TBuilder builder, PointF translate);
protected abstract void PrependRotationRadians(TBuilder builder, float radians);
protected abstract void PrependRotationRadians(TBuilder builder, float radians, Vector2 origin);
protected abstract void PrependScale(TBuilder builder, SizeF scale);
protected abstract void PrependSkewRadians(TBuilder builder, float radiansX, float radiansY);
protected abstract void PrependSkewRadians(TBuilder builder, float radiansX, float radiansY, Vector2 origin);
protected abstract void PrependTranslation(TBuilder builder, PointF translate);
protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint);
}
}