Skip to content

Commit

Permalink
Update refs and fix EXIF handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
JimBobSquarePants committed Jul 23, 2024
1 parent d1dd9d8 commit 5dd3dda
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 79 deletions.
17 changes: 8 additions & 9 deletions samples/ImageSharp.Web.Sample/Pages/IndexModel.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Defines the index page view model.
/// </summary>
public class IndexModel : PageModel
{
/// <summary>
/// Defines the index page view model.
/// </summary>
public class IndexModel : PageModel
{
}
}
80 changes: 26 additions & 54 deletions src/ImageSharp.Web/ExifOrientationUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,50 +25,34 @@ public static class ExifOrientationUtilities
/// </returns>
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,
};
}

/// <summary>
Expand Down Expand Up @@ -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;
}
4 changes: 2 additions & 2 deletions src/ImageSharp.Web/ImageSharp.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
</ItemGroup>

<Import Project="..\..\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />
Expand Down
27 changes: 24 additions & 3 deletions src/ImageSharp.Web/Processors/ResizeWebProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -162,6 +163,7 @@ private static Size ParseSize(
}

private static PointF? GetCenter(
FormattedImage image,
ushort orientation,
CommandCollection commands,
CommandParser parser,
Expand All @@ -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(
Expand Down Expand Up @@ -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));
}
2 changes: 1 addition & 1 deletion src/ImageSharp.Web/TagHelpers/HmacTokenTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class HmacTokenTagHelper : UrlResolutionTagHelper
/// <param name="options">The middleware configuration options.</param>
/// <param name="authorizationUtilities">Contains helpers that allow authorization of image requests.</param>
/// <param name="urlHelperFactory">The URL helper factory.</param>
/// <param name="htmlEncoder">The HTML encorder.</param>
/// <param name="htmlEncoder">The HTML encoder.</param>
public HmacTokenTagHelper(
IOptions<ImageSharpMiddlewareOptions> options,
RequestAuthorizationUtilities authorizationUtilities,
Expand Down
2 changes: 1 addition & 1 deletion src/ImageSharp.Web/TagHelpers/ImageTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public ImageTagHelper(

/// <summary>
/// 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.
/// </summary>
[HtmlAttributeName(AutoOrientAttributeName)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ public class ExifOrientationUtilitiesTests
public static TheoryData<Vector2, Vector2, Vector2, ushort, Vector2> TransformVectorData =

Check warning on line 26 in tests/ImageSharp.Web.Tests/Helpers/ExifOrientationUtilitiesTests.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net7.0, true, -x64, true)

Non-constant fields should not be visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2211)
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]
Expand Down

0 comments on commit 5dd3dda

Please sign in to comment.