Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update refs and fix EXIF handling. #374

Merged
merged 1 commit into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -23,18 +23,18 @@
// │ │ F│ │ │ │ │
// │ │ │ │ │ │ │
// └─────┴─────┴─────┴─────┴─────┴─────┘
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 All @@ -47,7 +47,7 @@
Assert.Equal(expected.Y, actual.Y, 4);
}

public static TheoryData<Size, ushort, Size> TransformSizeData =

Check warning on line 50 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 Size(150, 100), ExifOrientationMode.Unknown, new Size(150, 100) },
Expand All @@ -70,7 +70,7 @@
Assert.Equal(expected, actual);
}

public static TheoryData<AnchorPositionMode, ushort, AnchorPositionMode> TransformAnchorData =

Check warning on line 73 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()
{
{ AnchorPositionMode.Center, ExifOrientationMode.Unknown, AnchorPositionMode.Center },
Expand Down
Loading