Skip to content

Commit c308595

Browse files
Expose ExifOrientationUtilities
1 parent f91c093 commit c308595

6 files changed

Lines changed: 210 additions & 143 deletions

File tree

src/ImageSharp.Web/Caching/PhysicalFileSystemCache.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ internal static string GetCacheRoot(PhysicalFileSystemCacheOptions cacheOptions,
7878

7979
string cacheFolderPath = Path.Combine(cacheRootPath, cacheOptions.CacheFolder);
8080

81-
return PathUtils.EnsureTrailingSlash(cacheFolderPath);
81+
return PathUtilities.EnsureTrailingSlash(cacheFolderPath);
8282
}
8383

8484
/// <inheritdoc/>
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System.Numerics;
5+
using System.Runtime.CompilerServices;
6+
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
7+
using SixLabors.ImageSharp.Processing;
8+
9+
namespace SixLabors.ImageSharp.Web
10+
{
11+
/// <summary>
12+
/// Contains various helper methods for working with EXIF orientation values.
13+
/// </summary>
14+
public static class ExifOrientationUtilities
15+
{
16+
/// <summary>
17+
/// Transforms the specified vector value depending on the specified <paramref name="orientation" />.
18+
/// </summary>
19+
/// <param name="position">The input vector value.</param>
20+
/// <param name="min">The minimum bounds of the area of interest.</param>
21+
/// <param name="max">The maximum bounds of the area of interest.</param>
22+
/// <param name="orientation">The EXIF orientation.</param>
23+
/// <returns>
24+
/// The transformed vector.
25+
/// </returns>
26+
public static Vector2 Transform(Vector2 position, Vector2 min, Vector2 max, ushort orientation)
27+
{
28+
if (orientation is >= ExifOrientationMode.Unknown and <= ExifOrientationMode.TopLeft)
29+
{
30+
return position;
31+
}
32+
33+
// New XY is calculated based on flipping and rotating the input XY.
34+
// Coordinates are normalized to a range of 0-1 so we can pass a constant integer size to the transform builder.
35+
Vector2 normalized = Normalize(position, min, max);
36+
AffineTransformBuilder builder = new();
37+
Size size = new(1, 1);
38+
switch (orientation)
39+
{
40+
case ExifOrientationMode.TopRight:
41+
builder.AppendTranslation(new Vector2(size.Width - normalized.X, 0));
42+
break;
43+
case ExifOrientationMode.BottomRight:
44+
builder.AppendRotationDegrees(180);
45+
break;
46+
case ExifOrientationMode.BottomLeft:
47+
builder.AppendTranslation(new Vector2(0, size.Height - normalized.Y));
48+
break;
49+
case ExifOrientationMode.LeftTop:
50+
builder.AppendTranslation(new Vector2(size.Width - normalized.X, 0));
51+
builder.AppendRotationDegrees(270);
52+
break;
53+
case ExifOrientationMode.RightTop:
54+
builder.AppendRotationDegrees(270);
55+
break;
56+
case ExifOrientationMode.RightBottom:
57+
builder.AppendTranslation(new Vector2(size.Width - normalized.X, 0));
58+
builder.AppendRotationDegrees(90);
59+
break;
60+
case ExifOrientationMode.LeftBottom:
61+
builder.AppendRotationDegrees(90);
62+
break;
63+
default:
64+
return position;
65+
}
66+
67+
Matrix3x2 matrix = builder.BuildMatrix(size);
68+
return DeNormalize(Vector2.Transform(normalized, matrix), min, max);
69+
}
70+
71+
/// <summary>
72+
/// Transforms the specified size value depending on the specified <paramref name="orientation" />.
73+
/// </summary>
74+
/// <param name="size">The input size value.</param>
75+
/// <param name="orientation">The EXIF orientation.</param>
76+
/// <returns>
77+
/// The transformed size.
78+
/// </returns>
79+
public static Size Transform(Size size, ushort orientation)
80+
=> IsExifOrientationRotated(orientation)
81+
? new Size(size.Height, size.Width)
82+
: size;
83+
84+
/// <summary>
85+
/// Transforms the specified anchor value depending on the specified <paramref name="orientation" />.
86+
/// </summary>
87+
/// <param name="anchor">The input anchor value.</param>
88+
/// <param name="orientation">The EXIF orientation.</param>
89+
/// <returns>
90+
/// The transformed anchor.
91+
/// </returns>
92+
public static AnchorPositionMode Transform(AnchorPositionMode anchor, ushort orientation)
93+
{
94+
if (orientation is >= ExifOrientationMode.Unknown and <= ExifOrientationMode.TopLeft)
95+
{
96+
return anchor;
97+
}
98+
99+
/*
100+
New anchor position is determined by calculating the direction of the anchor relative to the source image.
101+
In the following example, the TopRight anchor becomes BottomRight
102+
103+
T L
104+
+-----------------------+ +--------------+
105+
| *| | |
106+
| | | |
107+
L | TL | R | |
108+
| | | |
109+
| | B | LB | T
110+
| | | |
111+
+-----------------------+ | |
112+
B | |
113+
| |
114+
| *|
115+
+--------------+
116+
R
117+
*/
118+
return anchor switch
119+
{
120+
AnchorPositionMode.Center => anchor,
121+
AnchorPositionMode.Top => orientation switch
122+
{
123+
ExifOrientationMode.BottomLeft or ExifOrientationMode.BottomRight => AnchorPositionMode.Bottom,
124+
ExifOrientationMode.LeftTop or ExifOrientationMode.RightTop => AnchorPositionMode.Left,
125+
ExifOrientationMode.LeftBottom or ExifOrientationMode.RightBottom => AnchorPositionMode.Right,
126+
_ => anchor,
127+
},
128+
AnchorPositionMode.Bottom => orientation switch
129+
{
130+
ExifOrientationMode.BottomLeft or ExifOrientationMode.BottomRight => AnchorPositionMode.Top,
131+
ExifOrientationMode.LeftTop or ExifOrientationMode.RightTop => AnchorPositionMode.Right,
132+
ExifOrientationMode.LeftBottom or ExifOrientationMode.RightBottom => AnchorPositionMode.Left,
133+
_ => anchor,
134+
},
135+
AnchorPositionMode.Left => orientation switch
136+
{
137+
ExifOrientationMode.TopRight or ExifOrientationMode.BottomRight => AnchorPositionMode.Right,
138+
ExifOrientationMode.LeftTop or ExifOrientationMode.LeftBottom => AnchorPositionMode.Top,
139+
ExifOrientationMode.RightTop or ExifOrientationMode.RightBottom => AnchorPositionMode.Bottom,
140+
_ => anchor,
141+
},
142+
AnchorPositionMode.Right => orientation switch
143+
{
144+
ExifOrientationMode.TopRight or ExifOrientationMode.BottomRight => AnchorPositionMode.Left,
145+
ExifOrientationMode.LeftTop or ExifOrientationMode.LeftBottom => AnchorPositionMode.Bottom,
146+
ExifOrientationMode.RightTop or ExifOrientationMode.RightBottom => AnchorPositionMode.Top,
147+
_ => anchor,
148+
},
149+
AnchorPositionMode.TopLeft => orientation switch
150+
{
151+
ExifOrientationMode.TopRight or ExifOrientationMode.LeftBottom => AnchorPositionMode.TopRight,
152+
ExifOrientationMode.BottomRight or ExifOrientationMode.RightTop => AnchorPositionMode.BottomLeft,
153+
ExifOrientationMode.BottomLeft or ExifOrientationMode.RightBottom => AnchorPositionMode.BottomRight,
154+
_ => anchor,
155+
},
156+
AnchorPositionMode.TopRight => orientation switch
157+
{
158+
ExifOrientationMode.TopRight or ExifOrientationMode.RightTop => AnchorPositionMode.TopLeft,
159+
ExifOrientationMode.BottomLeft or ExifOrientationMode.LeftBottom => AnchorPositionMode.BottomRight,
160+
ExifOrientationMode.BottomRight or ExifOrientationMode.LeftTop => AnchorPositionMode.BottomLeft,
161+
_ => anchor,
162+
},
163+
AnchorPositionMode.BottomRight => orientation switch
164+
{
165+
ExifOrientationMode.TopRight or ExifOrientationMode.LeftBottom => AnchorPositionMode.BottomLeft,
166+
ExifOrientationMode.BottomLeft or ExifOrientationMode.RightTop => AnchorPositionMode.TopRight,
167+
ExifOrientationMode.BottomRight or ExifOrientationMode.RightBottom => AnchorPositionMode.TopLeft,
168+
_ => anchor,
169+
},
170+
AnchorPositionMode.BottomLeft => orientation switch
171+
{
172+
ExifOrientationMode.TopRight or ExifOrientationMode.RightTop => AnchorPositionMode.BottomRight,
173+
ExifOrientationMode.BottomLeft or ExifOrientationMode.LeftBottom => AnchorPositionMode.TopLeft,
174+
ExifOrientationMode.BottomRight or ExifOrientationMode.LeftTop => AnchorPositionMode.TopRight,
175+
_ => anchor,
176+
},
177+
_ => anchor,
178+
};
179+
}
180+
181+
/// <summary>
182+
/// Returns a value indicating whether an EXIF orientation is rotated (not flipped).
183+
/// </summary>
184+
/// <param name="orientation">The EXIF orientation.</param>
185+
/// <returns><see langword="true"/> if the orientation value indicates rotation; otherwise <see langword="false"/>.</returns>
186+
public static bool IsExifOrientationRotated(ushort orientation)
187+
=> orientation switch
188+
{
189+
ExifOrientationMode.LeftTop
190+
or ExifOrientationMode.RightTop
191+
or ExifOrientationMode.RightBottom
192+
or ExifOrientationMode.LeftBottom => true,
193+
_ => false,
194+
};
195+
196+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
197+
private static Vector2 Normalize(Vector2 x, Vector2 min, Vector2 max) => (x - min) / (max - min);
198+
199+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
200+
private static Vector2 DeNormalize(Vector2 x, Vector2 min, Vector2 max) => min + (x * (max - min));
201+
}
202+
}

src/ImageSharp.Web/FormatUtilities.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
namespace SixLabors.ImageSharp.Web
1616
{
1717
/// <summary>
18-
/// Contains various helper methods based on the given configuration.
18+
/// Contains various helper methods for working with image formats based on the given configuration.
1919
/// </summary>
2020
public sealed class FormatUtilities
2121
{
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace SixLabors.ImageSharp.Web
88
{
9-
internal static class PathUtils
9+
internal static class PathUtilities
1010
{
1111
/// <summary>
1212
/// Ensures the path ends with a trailing slash (directory separator).

0 commit comments

Comments
 (0)