Browse Source

Convert OrientationMode into something usable.

pull/1976/head
James Jackson-South 4 years ago
parent
commit
d8372568e0
  1. 24
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs
  2. 36
      src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs
  3. 71
      tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs

24
src/ImageSharp/Processing/OrientationMode.cs → src/ImageSharp/Metadata/Profiles/Exif/Values/ExifOrientationMode.cs

@ -1,56 +1,56 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{ {
/// <summary> /// <summary>
/// Enumerates the available orientation values supplied by EXIF metadata. /// Enumerates the available orientation values supplied by EXIF metadata.
/// </summary> /// </summary>
internal enum OrientationMode : ushort public static class ExifOrientationMode
{ {
/// <summary> /// <summary>
/// Unknown rotation. /// Unknown rotation.
/// </summary> /// </summary>
Unknown = 0, public const ushort Unknown = 0;
/// <summary> /// <summary>
/// The 0th row at the top, the 0th column on the left. /// The 0th row at the top, the 0th column on the left.
/// </summary> /// </summary>
TopLeft = 1, public const ushort TopLeft = 1;
/// <summary> /// <summary>
/// The 0th row at the top, the 0th column on the right. /// The 0th row at the top, the 0th column on the right.
/// </summary> /// </summary>
TopRight = 2, public const ushort TopRight = 2;
/// <summary> /// <summary>
/// The 0th row at the bottom, the 0th column on the right. /// The 0th row at the bottom, the 0th column on the right.
/// </summary> /// </summary>
BottomRight = 3, public const ushort BottomRight = 3;
/// <summary> /// <summary>
/// The 0th row at the bottom, the 0th column on the left. /// The 0th row at the bottom, the 0th column on the left.
/// </summary> /// </summary>
BottomLeft = 4, public const ushort BottomLeft = 4;
/// <summary> /// <summary>
/// The 0th row on the left, the 0th column at the top. /// The 0th row on the left, the 0th column at the top.
/// </summary> /// </summary>
LeftTop = 5, public const ushort LeftTop = 5;
/// <summary> /// <summary>
/// The 0th row at the right, the 0th column at the top. /// The 0th row at the right, the 0th column at the top.
/// </summary> /// </summary>
RightTop = 6, public const ushort RightTop = 6;
/// <summary> /// <summary>
/// The 0th row on the right, the 0th column at the bottom. /// The 0th row on the right, the 0th column at the bottom.
/// </summary> /// </summary>
RightBottom = 7, public const ushort RightBottom = 7;
/// <summary> /// <summary>
/// The 0th row on the left, the 0th column at the bottom. /// The 0th row on the left, the 0th column at the bottom.
/// </summary> /// </summary>
LeftBottom = 8 public const ushort LeftBottom = 8;
} }
} }

36
src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs

@ -28,42 +28,42 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeImageApply() protected override void BeforeImageApply()
{ {
OrientationMode orientation = GetExifOrientation(this.Source); ushort orientation = GetExifOrientation(this.Source);
Size size = this.SourceRectangle.Size; Size size = this.SourceRectangle.Size;
switch (orientation) switch (orientation)
{ {
case OrientationMode.TopRight: case ExifOrientationMode.TopRight:
new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.BottomRight: case ExifOrientationMode.BottomRight:
new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.BottomLeft: case ExifOrientationMode.BottomLeft:
new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.LeftTop: case ExifOrientationMode.LeftTop:
new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle);
new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Horizontal).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.RightTop: case ExifOrientationMode.RightTop:
new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.RightBottom: case ExifOrientationMode.RightBottom:
new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Vertical).Execute(this.Configuration, this.Source, this.SourceRectangle);
new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.LeftBottom: case ExifOrientationMode.LeftBottom:
new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Configuration, this.Source, this.SourceRectangle);
break; break;
case OrientationMode.Unknown: case ExifOrientationMode.Unknown:
case OrientationMode.TopLeft: case ExifOrientationMode.TopLeft:
default: default:
break; break;
} }
@ -81,32 +81,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// Returns the current EXIF orientation /// Returns the current EXIF orientation
/// </summary> /// </summary>
/// <param name="source">The image to auto rotate.</param> /// <param name="source">The image to auto rotate.</param>
/// <returns>The <see cref="OrientationMode"/></returns> /// <returns>The <see cref="ushort"/></returns>
private static OrientationMode GetExifOrientation(Image<TPixel> source) private static ushort GetExifOrientation(Image<TPixel> source)
{ {
if (source.Metadata.ExifProfile is null) if (source.Metadata.ExifProfile is null)
{ {
return OrientationMode.Unknown; return ExifOrientationMode.Unknown;
} }
IExifValue<ushort> value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation); IExifValue<ushort> value = source.Metadata.ExifProfile.GetValue(ExifTag.Orientation);
if (value is null) if (value is null)
{ {
return OrientationMode.Unknown; return ExifOrientationMode.Unknown;
} }
OrientationMode orientation; ushort orientation;
if (value.DataType == ExifDataType.Short) if (value.DataType == ExifDataType.Short)
{ {
orientation = (OrientationMode)value.Value; orientation = value.Value;
} }
else else
{ {
orientation = (OrientationMode)Convert.ToUInt16(value.Value); orientation = Convert.ToUInt16(value.Value);
source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation); source.Metadata.ExifProfile.RemoveValue(ExifTag.Orientation);
} }
source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)OrientationMode.TopLeft); source.Metadata.ExifProfile.SetValue(ExifTag.Orientation, ExifOrientationMode.TopLeft);
return orientation; return orientation;
} }

71
tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs

@ -18,42 +18,41 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
public const string FlipTestFile = TestImages.Bmp.F; public const string FlipTestFile = TestImages.Bmp.F;
public static readonly TheoryData<ExifDataType, byte[]> InvalidOrientationValues public static readonly TheoryData<ExifDataType, byte[]> InvalidOrientationValues
= new TheoryData<ExifDataType, byte[]> = new()
{ {
{ ExifDataType.Byte, new byte[] { 1 } }, { ExifDataType.Byte, new byte[] { 1 } },
{ ExifDataType.SignedByte, new byte[] { 2 } }, { ExifDataType.SignedByte, new byte[] { 2 } },
{ ExifDataType.SignedShort, BitConverter.GetBytes((short)3) }, { ExifDataType.SignedShort, BitConverter.GetBytes((short)3) },
{ ExifDataType.Long, BitConverter.GetBytes(4U) }, { ExifDataType.Long, BitConverter.GetBytes(4U) },
{ ExifDataType.SignedLong, BitConverter.GetBytes(5) } { ExifDataType.SignedLong, BitConverter.GetBytes(5) }
}; };
public static readonly TheoryData<ushort> ExifOrientationValues = new TheoryData<ushort> public static readonly TheoryData<ushort> ExifOrientationValues
{ = new()
0, {
1, ExifOrientationMode.Unknown,
2, ExifOrientationMode.TopLeft,
3, ExifOrientationMode.TopRight,
4, ExifOrientationMode.BottomRight,
5, ExifOrientationMode.BottomLeft,
6, ExifOrientationMode.LeftTop,
7, ExifOrientationMode.RightTop,
8 ExifOrientationMode.RightBottom,
}; ExifOrientationMode.LeftBottom
};
[Theory] [Theory]
[WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)] [WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)]
public void AutoOrient_WorksForAllExifOrientations<TPixel>(TestImageProvider<TPixel> provider, ushort orientation) public void AutoOrient_WorksForAllExifOrientations<TPixel>(TestImageProvider<TPixel> provider, ushort orientation)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage()) using Image<TPixel> image = provider.GetImage();
{ image.Metadata.ExifProfile = new ExifProfile();
image.Metadata.ExifProfile = new ExifProfile(); image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation);
image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, orientation);
image.Mutate(x => x.AutoOrient()); image.Mutate(x => x.AutoOrient());
image.DebugSave(provider, orientation, appendPixelTypeToFileName: false); image.DebugSave(provider, orientation, appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false); image.CompareToReferenceOutput(provider, orientation, appendPixelTypeToFileName: false);
}
} }
[Theory] [Theory]
@ -76,19 +75,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
// Change the number of components // Change the number of components
bytes[20] = 1; bytes[20] = 1;
var orientationCodeData = new byte[8]; byte[] orientationCodeData = new byte[8];
Array.Copy(orientation, orientationCodeData, orientation.Length); Array.Copy(orientation, orientationCodeData, orientation.Length);
ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0); ulong orientationCode = BitConverter.ToUInt64(orientationCodeData, 0);
using (Image<TPixel> image = provider.GetImage()) using Image<TPixel> image = provider.GetImage();
using (Image<TPixel> reference = image.Clone()) using Image<TPixel> reference = image.Clone();
{ image.Metadata.ExifProfile = new ExifProfile(bytes);
image.Metadata.ExifProfile = new ExifProfile(bytes); image.Mutate(x => x.AutoOrient());
image.Mutate(x => x.AutoOrient()); image.DebugSave(provider, $"{dataType}-{orientationCode}", appendPixelTypeToFileName: false);
image.DebugSave(provider, $"{dataType}-{orientationCode}", appendPixelTypeToFileName: false); ImageComparer.Exact.VerifySimilarity(image, reference);
ImageComparer.Exact.VerifySimilarity(image, reference);
}
} }
} }
} }

Loading…
Cancel
Save