Skip to content

Commit 2656529

Browse files
Fix Vector2 Transform
1 parent 098c42f commit 2656529

3 files changed

Lines changed: 148 additions & 107 deletions

File tree

src/ImageSharp.Web/ExifOrientationUtilities.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public static Vector2 Transform(Vector2 position, Vector2 min, Vector2 max, usho
6666
}
6767

6868
Matrix3x2 matrix = builder.BuildMatrix(size);
69-
return DeScale(Vector2.Transform(scaled, matrix), min, max);
69+
return DeScale(Vector2.Transform(scaled, matrix), SwapXY(min, orientation), SwapXY(max, orientation));
7070
}
7171

7272
/// <summary>
@@ -202,5 +202,11 @@ or ExifOrientationMode.RightBottom
202202

203203
[MethodImpl(MethodImplOptions.AggressiveInlining)]
204204
private static float FlipScaled(float origin) => (2F * -origin) + 1F;
205+
206+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
207+
private static Vector2 SwapXY(Vector2 position, ushort orientation)
208+
=> IsExifOrientationRotated(orientation)
209+
? new Vector2(position.Y, position.X)
210+
: position;
205211
}
206212
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.Numerics;
5+
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
6+
using SixLabors.ImageSharp.Processing;
7+
using Xunit;
8+
9+
namespace SixLabors.ImageSharp.Web.Tests.Helpers
10+
{
11+
public class ExifOrientationUtilitiesTests
12+
{
13+
//   150
14+
// ┌─────┬─────┬─────┬─────┬─────┬─────┐
15+
// │ │ │ │ │ │ │
16+
// │ │ FFFFFFFFFFFFFFF │ │
17+
// ├─────X────F┌─────┬─────┬─────┼─────┤
18+
// │ │ F│ │ │ │ │
19+
// │ │ FFFFFFFFFFFFFFF │ │
20+
// ├─────┼────F┌─────┬─────┬─────┼─────┤ 100
21+
// │ │ F│ │ │ │ │
22+
// │ │ F│ │ │ │ │
23+
// ├─────┼────F├─────┼─────┼─────┼─────┤
24+
// │ │ F│ │ │ │ │
25+
// │ │ │ │ │ │ │
26+
// └─────┴─────┴─────┴─────┴─────┴─────┘
27+
public static TheoryData<Vector2, Vector2, Vector2, ushort, Vector2> TransformVectorData =
28+
new()
29+
{
30+
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.Unknown, new Vector2(25F, 25F) },
31+
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.TopLeft, new Vector2(25F, 25F) },
32+
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.TopRight, new Vector2(125F, 25F) },
33+
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.BottomRight, new Vector2(125F, 75F) },
34+
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.BottomLeft, new Vector2(25F, 75F) },
35+
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.LeftTop, new Vector2(25F, 25F) },
36+
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.RightTop, new Vector2(25F, 125F) },
37+
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.RightBottom, new Vector2(75F, 125F) },
38+
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.LeftBottom, new Vector2(75F, 25F) }
39+
};
40+
41+
[Theory]
42+
[MemberData(nameof(TransformVectorData))]
43+
public void CanTransformVector(Vector2 position, Vector2 min, Vector2 max, ushort orientation, Vector2 expected)
44+
{
45+
Vector2 actual = ExifOrientationUtilities.Transform(position, min, max, orientation);
46+
47+
Assert.Equal(expected.X, actual.X, 4);
48+
Assert.Equal(expected.Y, actual.Y, 4);
49+
}
50+
51+
public static TheoryData<Size, ushort, Size> TransformSizeData =
52+
new()
53+
{
54+
{ new Size(150, 100), ExifOrientationMode.Unknown, new Size(150, 100) },
55+
{ new Size(150, 100), ExifOrientationMode.TopLeft, new Size(150, 100) },
56+
{ new Size(150, 100), ExifOrientationMode.TopRight, new Size(150, 100) },
57+
{ new Size(150, 100), ExifOrientationMode.BottomRight, new Size(150, 100) },
58+
{ new Size(150, 100), ExifOrientationMode.BottomLeft, new Size(150, 100) },
59+
{ new Size(150, 100), ExifOrientationMode.LeftTop, new Size(100, 150) },
60+
{ new Size(150, 100), ExifOrientationMode.RightTop, new Size(100, 150) },
61+
{ new Size(150, 100), ExifOrientationMode.RightBottom, new Size(100, 150) },
62+
{ new Size(150, 100), ExifOrientationMode.LeftBottom, new Size(100, 150) },
63+
};
64+
65+
[Theory]
66+
[MemberData(nameof(TransformSizeData))]
67+
public void CanTransformSize(Size size, ushort orientation, Size expected)
68+
{
69+
Size actual = ExifOrientationUtilities.Transform(size, orientation);
70+
71+
Assert.Equal(expected, actual);
72+
}
73+
74+
public static TheoryData<AnchorPositionMode, ushort, AnchorPositionMode> TransformAnchorData =
75+
new()
76+
{
77+
{ AnchorPositionMode.Center, ExifOrientationMode.TopLeft, AnchorPositionMode.Center },
78+
{ AnchorPositionMode.Center, ExifOrientationMode.BottomLeft, AnchorPositionMode.Center },
79+
{ AnchorPositionMode.Center, ExifOrientationMode.LeftBottom, AnchorPositionMode.Center },
80+
{ AnchorPositionMode.Top, ExifOrientationMode.BottomLeft, AnchorPositionMode.Bottom },
81+
{ AnchorPositionMode.Top, ExifOrientationMode.BottomRight, AnchorPositionMode.Bottom },
82+
{ AnchorPositionMode.Top, ExifOrientationMode.LeftTop, AnchorPositionMode.Left },
83+
{ AnchorPositionMode.Top, ExifOrientationMode.RightTop, AnchorPositionMode.Left },
84+
{ AnchorPositionMode.Top, ExifOrientationMode.LeftBottom, AnchorPositionMode.Right },
85+
{ AnchorPositionMode.Top, ExifOrientationMode.RightBottom, AnchorPositionMode.Right },
86+
{ AnchorPositionMode.Bottom, ExifOrientationMode.BottomLeft, AnchorPositionMode.Top },
87+
{ AnchorPositionMode.Bottom, ExifOrientationMode.BottomRight, AnchorPositionMode.Top },
88+
{ AnchorPositionMode.Bottom, ExifOrientationMode.LeftTop, AnchorPositionMode.Right },
89+
{ AnchorPositionMode.Bottom, ExifOrientationMode.RightTop, AnchorPositionMode.Right },
90+
{ AnchorPositionMode.Bottom, ExifOrientationMode.LeftBottom, AnchorPositionMode.Left },
91+
{ AnchorPositionMode.Bottom, ExifOrientationMode.RightBottom, AnchorPositionMode.Left },
92+
{ AnchorPositionMode.Left, ExifOrientationMode.TopRight, AnchorPositionMode.Right },
93+
{ AnchorPositionMode.Left, ExifOrientationMode.BottomRight, AnchorPositionMode.Right },
94+
{ AnchorPositionMode.Left, ExifOrientationMode.LeftTop, AnchorPositionMode.Top },
95+
{ AnchorPositionMode.Left, ExifOrientationMode.LeftBottom, AnchorPositionMode.Top },
96+
{ AnchorPositionMode.Left, ExifOrientationMode.RightTop, AnchorPositionMode.Bottom },
97+
{ AnchorPositionMode.Left, ExifOrientationMode.RightBottom, AnchorPositionMode.Bottom },
98+
{ AnchorPositionMode.Right, ExifOrientationMode.TopRight, AnchorPositionMode.Left },
99+
{ AnchorPositionMode.Right, ExifOrientationMode.BottomRight, AnchorPositionMode.Left },
100+
{ AnchorPositionMode.Right, ExifOrientationMode.LeftTop, AnchorPositionMode.Bottom },
101+
{ AnchorPositionMode.Right, ExifOrientationMode.LeftBottom, AnchorPositionMode.Bottom },
102+
{ AnchorPositionMode.Right, ExifOrientationMode.RightTop, AnchorPositionMode.Top },
103+
{ AnchorPositionMode.Right, ExifOrientationMode.RightBottom, AnchorPositionMode.Top },
104+
{ AnchorPositionMode.TopLeft, ExifOrientationMode.TopRight, AnchorPositionMode.TopRight },
105+
{ AnchorPositionMode.TopLeft, ExifOrientationMode.LeftBottom, AnchorPositionMode.TopRight },
106+
{ AnchorPositionMode.TopLeft, ExifOrientationMode.BottomRight, AnchorPositionMode.BottomLeft },
107+
{ AnchorPositionMode.TopLeft, ExifOrientationMode.RightTop, AnchorPositionMode.BottomLeft },
108+
{ AnchorPositionMode.TopLeft, ExifOrientationMode.BottomLeft, AnchorPositionMode.BottomRight },
109+
{ AnchorPositionMode.TopLeft, ExifOrientationMode.RightBottom, AnchorPositionMode.BottomRight },
110+
{ AnchorPositionMode.TopRight, ExifOrientationMode.TopRight, AnchorPositionMode.TopLeft },
111+
{ AnchorPositionMode.TopRight, ExifOrientationMode.RightTop, AnchorPositionMode.TopLeft },
112+
{ AnchorPositionMode.TopRight, ExifOrientationMode.BottomLeft, AnchorPositionMode.BottomRight },
113+
{ AnchorPositionMode.TopRight, ExifOrientationMode.LeftBottom, AnchorPositionMode.BottomRight },
114+
{ AnchorPositionMode.TopRight, ExifOrientationMode.BottomRight, AnchorPositionMode.BottomLeft },
115+
{ AnchorPositionMode.TopRight, ExifOrientationMode.LeftTop, AnchorPositionMode.BottomLeft },
116+
{ AnchorPositionMode.BottomRight, ExifOrientationMode.TopRight, AnchorPositionMode.BottomLeft },
117+
{ AnchorPositionMode.BottomRight, ExifOrientationMode.LeftBottom, AnchorPositionMode.BottomLeft },
118+
{ AnchorPositionMode.BottomRight, ExifOrientationMode.BottomLeft, AnchorPositionMode.TopRight },
119+
{ AnchorPositionMode.BottomRight, ExifOrientationMode.RightTop, AnchorPositionMode.TopRight },
120+
{ AnchorPositionMode.BottomRight, ExifOrientationMode.BottomRight, AnchorPositionMode.TopLeft },
121+
{ AnchorPositionMode.BottomRight, ExifOrientationMode.RightBottom, AnchorPositionMode.TopLeft },
122+
{ AnchorPositionMode.BottomLeft, ExifOrientationMode.TopRight, AnchorPositionMode.BottomRight },
123+
{ AnchorPositionMode.BottomLeft, ExifOrientationMode.RightTop, AnchorPositionMode.BottomRight },
124+
{ AnchorPositionMode.BottomLeft, ExifOrientationMode.BottomLeft, AnchorPositionMode.TopLeft },
125+
{ AnchorPositionMode.BottomLeft, ExifOrientationMode.LeftBottom, AnchorPositionMode.TopLeft },
126+
{ AnchorPositionMode.BottomLeft, ExifOrientationMode.BottomRight, AnchorPositionMode.TopRight },
127+
{ AnchorPositionMode.BottomLeft, ExifOrientationMode.LeftTop, AnchorPositionMode.TopRight },
128+
};
129+
130+
[Theory]
131+
[MemberData(nameof(TransformAnchorData))]
132+
public void CanTransformAnchor(AnchorPositionMode anchor, ushort orientation, AnchorPositionMode expected)
133+
{
134+
AnchorPositionMode actual = ExifOrientationUtilities.Transform(anchor, orientation);
135+
136+
Assert.Equal(expected, actual);
137+
}
138+
}
139+
}

tests/ImageSharp.Web.Tests/Processors/ResizeWebProcessorTests.cs

Lines changed: 2 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public void ResizeWebProcessor_RespectsOrientation_Center(ushort orientation)
168168
image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation);
169169
using var formatted = new FormattedImage(image, PngFormat.Instance);
170170

171-
PointF expected = GetExpectedCenter(orientation, new PointF(x, y));
171+
PointF expected = ExifOrientationUtilities.Transform(new Vector2(x, y), Vector2.Zero, Vector2.One, orientation);
172172
ResizeOptions options = ResizeWebProcessor.GetResizeOptions(formatted, commands, parser, culture);
173173
Assert.Equal(expected, options.CenterCoordinates);
174174
}
@@ -213,7 +213,7 @@ public void ResizeWebProcessor_RespectsOrientation_Anchor(ushort orientation)
213213
{ new(ResizeWebProcessor.Anchor, anchor.ToString()) },
214214
};
215215

216-
AnchorPositionMode expected = GetExpectedAnchor(orientation, anchor);
216+
AnchorPositionMode expected = ExifOrientationUtilities.Transform(anchor, orientation);
217217
ResizeOptions options = ResizeWebProcessor.GetResizeOptions(formatted, commands, parser, culture);
218218
Assert.Equal(expected, options.Position);
219219
}
@@ -291,109 +291,5 @@ public void ResizeWebProcessor_CanReportAlphaRequirements(ResizeMode resizeMode,
291291

292292
Assert.Equal(requiresAlpha, new ResizeWebProcessor().RequiresTrueColorPixelFormat(commands, parser, culture));
293293
}
294-
295-
private static PointF GetExpectedCenter(ushort orientation, PointF center)
296-
{
297-
static float Flip(float origin) => (2F * -origin) + 1F;
298-
299-
// New XY is calculated based on flipping and rotating the input XY.
300-
// Coordinates range from 0-1, hence the matching source size.
301-
AffineTransformBuilder builder = new();
302-
Size size = new(1, 1);
303-
switch (orientation)
304-
{
305-
case ExifOrientationMode.TopRight:
306-
builder.AppendTranslation(new PointF(Flip(center.X), 0));
307-
break;
308-
case ExifOrientationMode.BottomRight:
309-
builder.AppendRotationDegrees(180);
310-
break;
311-
case ExifOrientationMode.BottomLeft:
312-
builder.AppendTranslation(new PointF(0, Flip(center.Y)));
313-
break;
314-
case ExifOrientationMode.LeftTop:
315-
builder.AppendTranslation(new PointF(Flip(center.X), 0));
316-
builder.AppendRotationDegrees(270);
317-
break;
318-
case ExifOrientationMode.RightTop:
319-
builder.AppendRotationDegrees(270);
320-
break;
321-
case ExifOrientationMode.RightBottom:
322-
builder.AppendTranslation(new PointF(Flip(center.X), 0));
323-
builder.AppendRotationDegrees(90);
324-
break;
325-
case ExifOrientationMode.LeftBottom:
326-
builder.AppendRotationDegrees(90);
327-
break;
328-
default:
329-
return center;
330-
}
331-
332-
Matrix3x2 matrix = builder.BuildMatrix(size);
333-
return Vector2.Transform(center, matrix);
334-
}
335-
336-
private static AnchorPositionMode GetExpectedAnchor(ushort orientation, AnchorPositionMode anchor)
337-
=> anchor switch
338-
{
339-
AnchorPositionMode.Center => anchor,
340-
AnchorPositionMode.Top => orientation switch
341-
{
342-
ExifOrientationMode.BottomLeft or ExifOrientationMode.BottomRight => AnchorPositionMode.Bottom,
343-
ExifOrientationMode.LeftTop or ExifOrientationMode.RightTop => AnchorPositionMode.Left,
344-
ExifOrientationMode.LeftBottom or ExifOrientationMode.RightBottom => AnchorPositionMode.Right,
345-
_ => anchor,
346-
},
347-
AnchorPositionMode.Bottom => orientation switch
348-
{
349-
ExifOrientationMode.BottomLeft or ExifOrientationMode.BottomRight => AnchorPositionMode.Top,
350-
ExifOrientationMode.LeftTop or ExifOrientationMode.RightTop => AnchorPositionMode.Right,
351-
ExifOrientationMode.LeftBottom or ExifOrientationMode.RightBottom => AnchorPositionMode.Left,
352-
_ => anchor,
353-
},
354-
AnchorPositionMode.Left => orientation switch
355-
{
356-
ExifOrientationMode.TopRight or ExifOrientationMode.BottomRight => AnchorPositionMode.Right,
357-
ExifOrientationMode.LeftTop or ExifOrientationMode.LeftBottom => AnchorPositionMode.Top,
358-
ExifOrientationMode.RightTop or ExifOrientationMode.RightBottom => AnchorPositionMode.Bottom,
359-
_ => anchor,
360-
},
361-
AnchorPositionMode.Right => orientation switch
362-
{
363-
ExifOrientationMode.TopRight or ExifOrientationMode.BottomRight => AnchorPositionMode.Left,
364-
ExifOrientationMode.LeftTop or ExifOrientationMode.LeftBottom => AnchorPositionMode.Bottom,
365-
ExifOrientationMode.RightTop or ExifOrientationMode.RightBottom => AnchorPositionMode.Top,
366-
_ => anchor,
367-
},
368-
AnchorPositionMode.TopLeft => orientation switch
369-
{
370-
ExifOrientationMode.TopRight or ExifOrientationMode.LeftBottom => AnchorPositionMode.TopRight,
371-
ExifOrientationMode.BottomRight or ExifOrientationMode.RightTop => AnchorPositionMode.BottomLeft,
372-
ExifOrientationMode.BottomLeft or ExifOrientationMode.RightBottom => AnchorPositionMode.BottomRight,
373-
_ => anchor,
374-
},
375-
AnchorPositionMode.TopRight => orientation switch
376-
{
377-
ExifOrientationMode.TopRight or ExifOrientationMode.RightTop => AnchorPositionMode.TopLeft,
378-
ExifOrientationMode.BottomLeft or ExifOrientationMode.LeftBottom => AnchorPositionMode.BottomRight,
379-
ExifOrientationMode.BottomRight or ExifOrientationMode.LeftTop => AnchorPositionMode.BottomLeft,
380-
_ => anchor,
381-
},
382-
AnchorPositionMode.BottomRight => orientation switch
383-
{
384-
ExifOrientationMode.TopRight or ExifOrientationMode.LeftBottom => AnchorPositionMode.BottomLeft,
385-
ExifOrientationMode.BottomLeft or ExifOrientationMode.RightTop => AnchorPositionMode.TopRight,
386-
ExifOrientationMode.BottomRight or ExifOrientationMode.RightBottom => AnchorPositionMode.TopLeft,
387-
_ => anchor,
388-
},
389-
AnchorPositionMode.BottomLeft => orientation switch
390-
{
391-
ExifOrientationMode.TopRight or ExifOrientationMode.RightTop => AnchorPositionMode.BottomRight,
392-
ExifOrientationMode.BottomLeft or ExifOrientationMode.LeftBottom => AnchorPositionMode.TopLeft,
393-
ExifOrientationMode.BottomRight or ExifOrientationMode.LeftTop => AnchorPositionMode.TopRight,
394-
_ => anchor,
395-
},
396-
_ => anchor,
397-
};
398294
}
399295
}

0 commit comments

Comments
 (0)