Skip to content

Commit 17fde8a

Browse files
Merge pull request #374 from SixLabors/js/update-refs
Update refs and fix EXIF handling.
2 parents d1dd9d8 + 5dd3dda commit 17fde8a

File tree

7 files changed

+71
-79
lines changed

7 files changed

+71
-79
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
// Copyright (c) Six Labors.
2-
// Licensed under the Apache License, Version 2.0.
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
33

44
using Microsoft.AspNetCore.Mvc.RazorPages;
55

6-
namespace ImageSharp.Web.Sample.Pages
6+
namespace ImageSharp.Web.Sample.Pages;
7+
8+
/// <summary>
9+
/// Defines the index page view model.
10+
/// </summary>
11+
public class IndexModel : PageModel
712
{
8-
/// <summary>
9-
/// Defines the index page view model.
10-
/// </summary>
11-
public class IndexModel : PageModel
12-
{
13-
}
1413
}

src/ImageSharp.Web/ExifOrientationUtilities.cs

+26-54
Original file line numberDiff line numberDiff line change
@@ -25,50 +25,34 @@ public static class ExifOrientationUtilities
2525
/// </returns>
2626
public static Vector2 Transform(Vector2 position, Vector2 min, Vector2 max, ushort orientation)
2727
{
28-
if (orientation is <= ExifOrientationMode.TopLeft or > ExifOrientationMode.LeftBottom)
29-
{
30-
// Short circuit orientations that are not transformed below
31-
return position;
32-
}
33-
3428
// New XY is calculated based on flipping and rotating the input XY.
35-
// Coordinate ranges are normalized to a range of 0-1 so we can pass a
36-
// constant integer size to the transform builder.
37-
Vector2 scaled = Scale(position, min, max);
38-
AffineTransformBuilder builder = new();
39-
Size size = new(1, 1);
40-
switch (orientation)
29+
Vector2 bounds = max - min;
30+
return orientation switch
4131
{
42-
case ExifOrientationMode.TopRight:
43-
builder.AppendTranslation(new Vector2(FlipScaled(scaled.X), 0));
44-
break;
45-
case ExifOrientationMode.BottomRight:
46-
builder.AppendRotationDegrees(180);
47-
break;
48-
case ExifOrientationMode.BottomLeft:
49-
builder.AppendTranslation(new Vector2(0, FlipScaled(scaled.Y)));
50-
break;
51-
case ExifOrientationMode.LeftTop:
52-
builder.AppendTranslation(new Vector2(FlipScaled(scaled.X), 0));
53-
builder.AppendRotationDegrees(270);
54-
break;
55-
case ExifOrientationMode.RightTop:
56-
builder.AppendRotationDegrees(270);
57-
break;
58-
case ExifOrientationMode.RightBottom:
59-
builder.AppendTranslation(new Vector2(FlipScaled(scaled.X), 0));
60-
builder.AppendRotationDegrees(90);
61-
break;
62-
case ExifOrientationMode.LeftBottom:
63-
builder.AppendRotationDegrees(90);
64-
break;
65-
default:
66-
// Use identity matrix.
67-
break;
68-
}
32+
// 0 degrees, mirrored: image has been flipped back-to-front.
33+
ExifOrientationMode.TopRight => new Vector2(Flip(position.X, bounds.X), position.Y),
34+
35+
// 180 degrees: image is upside down.
36+
ExifOrientationMode.BottomRight => new Vector2(Flip(position.X, bounds.X), Flip(position.Y, bounds.Y)),
37+
38+
// 180 degrees, mirrored: image has been flipped back-to-front and is upside down.
39+
ExifOrientationMode.BottomLeft => new Vector2(position.X, Flip(position.Y, bounds.Y)),
40+
41+
// 90 degrees: image has been flipped back-to-front and is on its side.
42+
ExifOrientationMode.LeftTop => new Vector2(position.Y, position.X),
43+
44+
// 90 degrees, mirrored: image is on its side.
45+
ExifOrientationMode.RightTop => new Vector2(position.Y, Flip(position.X, bounds.X)),
46+
47+
// 270 degrees: image has been flipped back-to-front and is on its far side.
48+
ExifOrientationMode.RightBottom => new Vector2(Flip(position.Y, bounds.Y), Flip(position.X, bounds.X)),
49+
50+
// 270 degrees, mirrored: image is on its far side.
51+
ExifOrientationMode.LeftBottom => new Vector2(Flip(position.Y, bounds.Y), position.X),
6952

70-
Matrix3x2 matrix = builder.BuildMatrix(size);
71-
return DeScale(Vector2.Transform(scaled, matrix), SwapXY(min, orientation), SwapXY(max, orientation));
53+
// 0 degrees: the correct orientation, no adjustment is required.
54+
_ => position,
55+
};
7256
}
7357

7458
/// <summary>
@@ -223,17 +207,5 @@ or ExifOrientationMode.RightBottom
223207
};
224208

225209
[MethodImpl(MethodImplOptions.AggressiveInlining)]
226-
private static Vector2 Scale(Vector2 x, Vector2 min, Vector2 max) => (x - min) / (max - min);
227-
228-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
229-
private static Vector2 DeScale(Vector2 x, Vector2 min, Vector2 max) => min + (x * (max - min));
230-
231-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
232-
private static float FlipScaled(float origin) => (2F * -origin) + 1F;
233-
234-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
235-
private static Vector2 SwapXY(Vector2 position, ushort orientation)
236-
=> IsExifOrientationRotated(orientation)
237-
? new Vector2(position.Y, position.X)
238-
: position;
210+
private static float Flip(float offset, float max) => max - offset;
239211
}

src/ImageSharp.Web/ImageSharp.Web.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545

4646
<ItemGroup>
4747
<FrameworkReference Include="Microsoft.AspNetCore.App" />
48-
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
49-
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
48+
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
49+
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
5050
</ItemGroup>
5151

5252
<Import Project="..\..\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />

src/ImageSharp.Web/Processors/ResizeWebProcessor.cs

+24-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Globalization;
55
using System.Numerics;
6+
using System.Runtime.CompilerServices;
67
using Microsoft.Extensions.Logging;
78
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
89
using SixLabors.ImageSharp.Processing;
@@ -131,7 +132,7 @@ public FormattedImage Process(
131132
return new()
132133
{
133134
Size = size,
134-
CenterCoordinates = GetCenter(orientation, commands, parser, culture),
135+
CenterCoordinates = GetCenter(image, orientation, commands, parser, culture),
135136
Position = GetAnchor(orientation, commands, parser, culture),
136137
Mode = mode,
137138
Compand = GetCompandMode(commands, parser, culture),
@@ -162,6 +163,7 @@ private static Size ParseSize(
162163
}
163164

164165
private static PointF? GetCenter(
166+
FormattedImage image,
165167
ushort orientation,
166168
CommandCollection commands,
167169
CommandParser parser,
@@ -179,8 +181,21 @@ private static Size ParseSize(
179181
return null;
180182
}
181183

182-
Vector2 center = new(coordinates[0], coordinates[1]);
183-
return ExifOrientationUtilities.Transform(center, Vector2.Zero, Vector2.One, orientation);
184+
// Coordinates for the center point are given as a percentage.
185+
// We must convert these to pixel values for transformation then convert back.
186+
//
187+
// Get the display size of the image after orientation is applied.
188+
Size size = ExifOrientationUtilities.Transform(new Size(image.Image.Width, image.Image.Height), orientation);
189+
Vector2 min = Vector2.Zero;
190+
Vector2 max = new(size.Width, size.Height);
191+
192+
// Scale pixel values up to image height and transform.
193+
Vector2 center = DeScale(new Vector2(coordinates[0], coordinates[1]), min, max);
194+
Vector2 transformed = ExifOrientationUtilities.Transform(center, min, max, orientation);
195+
196+
// Now scale pixel values down as percentage of real image height.
197+
max = new Vector2(image.Image.Width, image.Image.Height);
198+
return Scale(transformed, min, max);
184199
}
185200

186201
private static ResizeMode GetMode(
@@ -252,4 +267,10 @@ private static ushort GetExifOrientation(FormattedImage image, CommandCollection
252267
image.TryGetExifOrientation(out ushort orientation);
253268
return orientation;
254269
}
270+
271+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
272+
private static Vector2 Scale(Vector2 x, Vector2 min, Vector2 max) => (x - min) / (max - min);
273+
274+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
275+
private static Vector2 DeScale(Vector2 x, Vector2 min, Vector2 max) => min + (x * (max - min));
255276
}

src/ImageSharp.Web/TagHelpers/HmacTokenTagHelper.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class HmacTokenTagHelper : UrlResolutionTagHelper
2929
/// <param name="options">The middleware configuration options.</param>
3030
/// <param name="authorizationUtilities">Contains helpers that allow authorization of image requests.</param>
3131
/// <param name="urlHelperFactory">The URL helper factory.</param>
32-
/// <param name="htmlEncoder">The HTML encorder.</param>
32+
/// <param name="htmlEncoder">The HTML encoder.</param>
3333
public HmacTokenTagHelper(
3434
IOptions<ImageSharpMiddlewareOptions> options,
3535
RequestAuthorizationUtilities authorizationUtilities,

src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public ImageTagHelper(
154154

155155
/// <summary>
156156
/// Gets or sets a value indicating whether to automatically
157-
/// rotate/flip the iput image based on embedded EXIF orientation property values
157+
/// rotate/flip the input image based on embedded EXIF orientation property values
158158
/// before processing.
159159
/// </summary>
160160
[HtmlAttributeName(AutoOrientAttributeName)]

tests/ImageSharp.Web.Tests/Helpers/ExifOrientationUtilitiesTests.cs

+9-9
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ public class ExifOrientationUtilitiesTests
2626
public static TheoryData<Vector2, Vector2, Vector2, ushort, Vector2> TransformVectorData =
2727
new()
2828
{
29-
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.Unknown, new Vector2(25F, 25F) },
30-
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.TopLeft, new Vector2(25F, 25F) },
31-
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.TopRight, new Vector2(125F, 25F) },
32-
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.BottomRight, new Vector2(125F, 75F) },
33-
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.BottomLeft, new Vector2(25F, 75F) },
34-
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.LeftTop, new Vector2(25F, 25F) },
35-
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.RightTop, new Vector2(25F, 125F) },
36-
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.RightBottom, new Vector2(75F, 125F) },
37-
{ new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.LeftBottom, new Vector2(75F, 25F) },
29+
{ new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.Unknown, new Vector2(24F, 26F) },
30+
{ new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.TopLeft, new Vector2(24F, 26F) },
31+
{ new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.TopRight, new Vector2(126F, 26F) },
32+
{ new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.BottomRight, new Vector2(126F, 74F) },
33+
{ new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.BottomLeft, new Vector2(24F, 74F) },
34+
{ new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.LeftTop, new Vector2(26F, 24F) },
35+
{ new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.RightTop, new Vector2(26F, 126F) },
36+
{ new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.RightBottom, new Vector2(74F, 126F) },
37+
{ new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.LeftBottom, new Vector2(74F, 24F) },
3838
};
3939

4040
[Theory]

0 commit comments

Comments
 (0)