diff --git a/samples/ImageSharp.Web.Sample/Pages/IndexModel.cs b/samples/ImageSharp.Web.Sample/Pages/IndexModel.cs
index fd22ab50..eb1f835e 100644
--- a/samples/ImageSharp.Web.Sample/Pages/IndexModel.cs
+++ b/samples/ImageSharp.Web.Sample/Pages/IndexModel.cs
@@ -1,14 +1,13 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
using Microsoft.AspNetCore.Mvc.RazorPages;
-namespace ImageSharp.Web.Sample.Pages
+namespace ImageSharp.Web.Sample.Pages;
+
+///
+/// Defines the index page view model.
+///
+public class IndexModel : PageModel
{
- ///
- /// Defines the index page view model.
- ///
- public class IndexModel : PageModel
- {
- }
}
diff --git a/src/ImageSharp.Web/ExifOrientationUtilities.cs b/src/ImageSharp.Web/ExifOrientationUtilities.cs
index 2995c378..bd588e25 100644
--- a/src/ImageSharp.Web/ExifOrientationUtilities.cs
+++ b/src/ImageSharp.Web/ExifOrientationUtilities.cs
@@ -25,50 +25,34 @@ public static class ExifOrientationUtilities
///
public static Vector2 Transform(Vector2 position, Vector2 min, Vector2 max, ushort orientation)
{
- if (orientation is <= ExifOrientationMode.TopLeft or > ExifOrientationMode.LeftBottom)
- {
- // Short circuit orientations that are not transformed below
- return position;
- }
-
// New XY is calculated based on flipping and rotating the input XY.
- // Coordinate ranges are normalized to a range of 0-1 so we can pass a
- // constant integer size to the transform builder.
- Vector2 scaled = Scale(position, min, max);
- AffineTransformBuilder builder = new();
- Size size = new(1, 1);
- switch (orientation)
+ Vector2 bounds = max - min;
+ return orientation switch
{
- case ExifOrientationMode.TopRight:
- builder.AppendTranslation(new Vector2(FlipScaled(scaled.X), 0));
- break;
- case ExifOrientationMode.BottomRight:
- builder.AppendRotationDegrees(180);
- break;
- case ExifOrientationMode.BottomLeft:
- builder.AppendTranslation(new Vector2(0, FlipScaled(scaled.Y)));
- break;
- case ExifOrientationMode.LeftTop:
- builder.AppendTranslation(new Vector2(FlipScaled(scaled.X), 0));
- builder.AppendRotationDegrees(270);
- break;
- case ExifOrientationMode.RightTop:
- builder.AppendRotationDegrees(270);
- break;
- case ExifOrientationMode.RightBottom:
- builder.AppendTranslation(new Vector2(FlipScaled(scaled.X), 0));
- builder.AppendRotationDegrees(90);
- break;
- case ExifOrientationMode.LeftBottom:
- builder.AppendRotationDegrees(90);
- break;
- default:
- // Use identity matrix.
- break;
- }
+ // 0 degrees, mirrored: image has been flipped back-to-front.
+ ExifOrientationMode.TopRight => new Vector2(Flip(position.X, bounds.X), position.Y),
+
+ // 180 degrees: image is upside down.
+ ExifOrientationMode.BottomRight => new Vector2(Flip(position.X, bounds.X), Flip(position.Y, bounds.Y)),
+
+ // 180 degrees, mirrored: image has been flipped back-to-front and is upside down.
+ ExifOrientationMode.BottomLeft => new Vector2(position.X, Flip(position.Y, bounds.Y)),
+
+ // 90 degrees: image has been flipped back-to-front and is on its side.
+ ExifOrientationMode.LeftTop => new Vector2(position.Y, position.X),
+
+ // 90 degrees, mirrored: image is on its side.
+ ExifOrientationMode.RightTop => new Vector2(position.Y, Flip(position.X, bounds.X)),
+
+ // 270 degrees: image has been flipped back-to-front and is on its far side.
+ ExifOrientationMode.RightBottom => new Vector2(Flip(position.Y, bounds.Y), Flip(position.X, bounds.X)),
+
+ // 270 degrees, mirrored: image is on its far side.
+ ExifOrientationMode.LeftBottom => new Vector2(Flip(position.Y, bounds.Y), position.X),
- Matrix3x2 matrix = builder.BuildMatrix(size);
- return DeScale(Vector2.Transform(scaled, matrix), SwapXY(min, orientation), SwapXY(max, orientation));
+ // 0 degrees: the correct orientation, no adjustment is required.
+ _ => position,
+ };
}
///
@@ -223,17 +207,5 @@ or ExifOrientationMode.RightBottom
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Vector2 Scale(Vector2 x, Vector2 min, Vector2 max) => (x - min) / (max - min);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Vector2 DeScale(Vector2 x, Vector2 min, Vector2 max) => min + (x * (max - min));
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static float FlipScaled(float origin) => (2F * -origin) + 1F;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static Vector2 SwapXY(Vector2 position, ushort orientation)
- => IsExifOrientationRotated(orientation)
- ? new Vector2(position.Y, position.X)
- : position;
+ private static float Flip(float offset, float max) => max - offset;
}
diff --git a/src/ImageSharp.Web/ImageSharp.Web.csproj b/src/ImageSharp.Web/ImageSharp.Web.csproj
index 7a1f1f3e..6330edb6 100644
--- a/src/ImageSharp.Web/ImageSharp.Web.csproj
+++ b/src/ImageSharp.Web/ImageSharp.Web.csproj
@@ -45,8 +45,8 @@
-
-
+
+
diff --git a/src/ImageSharp.Web/Processors/ResizeWebProcessor.cs b/src/ImageSharp.Web/Processors/ResizeWebProcessor.cs
index 40898561..4004bfba 100644
--- a/src/ImageSharp.Web/Processors/ResizeWebProcessor.cs
+++ b/src/ImageSharp.Web/Processors/ResizeWebProcessor.cs
@@ -3,6 +3,7 @@
using System.Globalization;
using System.Numerics;
+using System.Runtime.CompilerServices;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Processing;
@@ -131,7 +132,7 @@ public FormattedImage Process(
return new()
{
Size = size,
- CenterCoordinates = GetCenter(orientation, commands, parser, culture),
+ CenterCoordinates = GetCenter(image, orientation, commands, parser, culture),
Position = GetAnchor(orientation, commands, parser, culture),
Mode = mode,
Compand = GetCompandMode(commands, parser, culture),
@@ -162,6 +163,7 @@ private static Size ParseSize(
}
private static PointF? GetCenter(
+ FormattedImage image,
ushort orientation,
CommandCollection commands,
CommandParser parser,
@@ -179,8 +181,21 @@ private static Size ParseSize(
return null;
}
- Vector2 center = new(coordinates[0], coordinates[1]);
- return ExifOrientationUtilities.Transform(center, Vector2.Zero, Vector2.One, orientation);
+ // Coordinates for the center point are given as a percentage.
+ // We must convert these to pixel values for transformation then convert back.
+ //
+ // Get the display size of the image after orientation is applied.
+ Size size = ExifOrientationUtilities.Transform(new Size(image.Image.Width, image.Image.Height), orientation);
+ Vector2 min = Vector2.Zero;
+ Vector2 max = new(size.Width, size.Height);
+
+ // Scale pixel values up to image height and transform.
+ Vector2 center = DeScale(new Vector2(coordinates[0], coordinates[1]), min, max);
+ Vector2 transformed = ExifOrientationUtilities.Transform(center, min, max, orientation);
+
+ // Now scale pixel values down as percentage of real image height.
+ max = new Vector2(image.Image.Width, image.Image.Height);
+ return Scale(transformed, min, max);
}
private static ResizeMode GetMode(
@@ -252,4 +267,10 @@ private static ushort GetExifOrientation(FormattedImage image, CommandCollection
image.TryGetExifOrientation(out ushort orientation);
return orientation;
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector2 Scale(Vector2 x, Vector2 min, Vector2 max) => (x - min) / (max - min);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector2 DeScale(Vector2 x, Vector2 min, Vector2 max) => min + (x * (max - min));
}
diff --git a/src/ImageSharp.Web/TagHelpers/HmacTokenTagHelper.cs b/src/ImageSharp.Web/TagHelpers/HmacTokenTagHelper.cs
index 3aef7500..81e25622 100644
--- a/src/ImageSharp.Web/TagHelpers/HmacTokenTagHelper.cs
+++ b/src/ImageSharp.Web/TagHelpers/HmacTokenTagHelper.cs
@@ -29,7 +29,7 @@ public class HmacTokenTagHelper : UrlResolutionTagHelper
/// The middleware configuration options.
/// Contains helpers that allow authorization of image requests.
/// The URL helper factory.
- /// The HTML encorder.
+ /// The HTML encoder.
public HmacTokenTagHelper(
IOptions options,
RequestAuthorizationUtilities authorizationUtilities,
diff --git a/src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs b/src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs
index 72b5c2cd..cf7c675e 100644
--- a/src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs
+++ b/src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs
@@ -154,7 +154,7 @@ public ImageTagHelper(
///
/// Gets or sets a value indicating whether to automatically
- /// rotate/flip the iput image based on embedded EXIF orientation property values
+ /// rotate/flip the input image based on embedded EXIF orientation property values
/// before processing.
///
[HtmlAttributeName(AutoOrientAttributeName)]
diff --git a/tests/ImageSharp.Web.Tests/Helpers/ExifOrientationUtilitiesTests.cs b/tests/ImageSharp.Web.Tests/Helpers/ExifOrientationUtilitiesTests.cs
index c553a743..497c3029 100644
--- a/tests/ImageSharp.Web.Tests/Helpers/ExifOrientationUtilitiesTests.cs
+++ b/tests/ImageSharp.Web.Tests/Helpers/ExifOrientationUtilitiesTests.cs
@@ -26,15 +26,15 @@ public class ExifOrientationUtilitiesTests
public static TheoryData TransformVectorData =
new()
{
- { new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.Unknown, new Vector2(25F, 25F) },
- { new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.TopLeft, new Vector2(25F, 25F) },
- { new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.TopRight, new Vector2(125F, 25F) },
- { new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.BottomRight, new Vector2(125F, 75F) },
- { new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.BottomLeft, new Vector2(25F, 75F) },
- { new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.LeftTop, new Vector2(25F, 25F) },
- { new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.RightTop, new Vector2(25F, 125F) },
- { new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.RightBottom, new Vector2(75F, 125F) },
- { new Vector2(25F, 25F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.LeftBottom, new Vector2(75F, 25F) },
+ { new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.Unknown, new Vector2(24F, 26F) },
+ { new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.TopLeft, new Vector2(24F, 26F) },
+ { new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.TopRight, new Vector2(126F, 26F) },
+ { new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.BottomRight, new Vector2(126F, 74F) },
+ { new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.BottomLeft, new Vector2(24F, 74F) },
+ { new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.LeftTop, new Vector2(26F, 24F) },
+ { new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.RightTop, new Vector2(26F, 126F) },
+ { new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.RightBottom, new Vector2(74F, 126F) },
+ { new Vector2(24F, 26F), Vector2.Zero, new Vector2(150, 100), ExifOrientationMode.LeftBottom, new Vector2(74F, 24F) },
};
[Theory]