Skip to content

Commit abf46b7

Browse files
Merge pull request #2944 from SixLabors/js/v3-issue-2906
V3 Use EXIF byte order for EXIF encoded strings.
2 parents 9c011b7 + 0261b9d commit abf46b7

File tree

7 files changed

+87
-15
lines changed

7 files changed

+87
-15
lines changed

src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,37 +50,66 @@ private static Encoding JIS0208Encoding
5050
_ => UndefinedCodeBytes
5151
};
5252

53-
public static Encoding GetEncoding(CharacterCode code) => code switch
53+
public static Encoding GetEncoding(CharacterCode code, ByteOrder order) => code switch
5454
{
5555
CharacterCode.ASCII => Encoding.ASCII,
5656
CharacterCode.JIS => JIS0208Encoding,
57-
CharacterCode.Unicode => Encoding.Unicode,
57+
CharacterCode.Unicode => order is ByteOrder.BigEndian ? Encoding.BigEndianUnicode : Encoding.Unicode,
5858
CharacterCode.Undefined => Encoding.UTF8,
5959
_ => Encoding.UTF8
6060
};
6161

62-
public static bool TryParse(ReadOnlySpan<byte> buffer, out EncodedString encodedString)
62+
public static bool TryParse(ReadOnlySpan<byte> buffer, ByteOrder order, out EncodedString encodedString)
6363
{
6464
if (TryDetect(buffer, out CharacterCode code))
6565
{
66-
string text = GetEncoding(code).GetString(buffer[CharacterCodeBytesLength..]);
67-
encodedString = new EncodedString(code, text);
68-
return true;
66+
ReadOnlySpan<byte> textBuffer = buffer[CharacterCodeBytesLength..];
67+
if (code == CharacterCode.Unicode && textBuffer.Length >= 2)
68+
{
69+
// Check BOM
70+
if (textBuffer[0] == 0xFF && textBuffer[1] == 0xFE)
71+
{
72+
// Little-endian BOM
73+
string text = Encoding.Unicode.GetString(textBuffer[2..]);
74+
encodedString = new EncodedString(code, text);
75+
return true;
76+
}
77+
else if (textBuffer[0] == 0xFE && textBuffer[1] == 0xFF)
78+
{
79+
// Big-endian BOM
80+
string text = Encoding.BigEndianUnicode.GetString(textBuffer[2..]);
81+
encodedString = new EncodedString(code, text);
82+
return true;
83+
}
84+
else
85+
{
86+
// No BOM, use EXIF byte order
87+
string text = GetEncoding(code, order).GetString(textBuffer);
88+
encodedString = new EncodedString(code, text);
89+
return true;
90+
}
91+
}
92+
else
93+
{
94+
string text = GetEncoding(code, order).GetString(textBuffer);
95+
encodedString = new EncodedString(code, text);
96+
return true;
97+
}
6998
}
7099

71100
encodedString = default;
72101
return false;
73102
}
74103

75104
public static uint GetDataLength(EncodedString encodedString) =>
76-
(uint)GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + CharacterCodeBytesLength;
105+
(uint)GetEncoding(encodedString.Code, ByteOrder.LittleEndian).GetByteCount(encodedString.Text) + CharacterCodeBytesLength;
77106

78107
public static int Write(EncodedString encodedString, Span<byte> destination)
79108
{
80109
GetCodeBytes(encodedString.Code).CopyTo(destination);
81110

82111
string text = encodedString.Text;
83-
int count = Write(GetEncoding(encodedString.Code), text, destination[CharacterCodeBytesLength..]);
112+
int count = Write(GetEncoding(encodedString.Code, ByteOrder.LittleEndian), text, destination[CharacterCodeBytesLength..]);
84113

85114
return CharacterCodeBytesLength + count;
86115
}

src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ internal abstract class BaseExifReader
9090
private readonly MemoryAllocator? allocator;
9191
private readonly Stream data;
9292
private List<ExifTag>? invalidTags;
93-
9493
private List<ulong>? subIfds;
94+
private bool isBigEndian;
9595

9696
protected BaseExifReader(Stream stream, MemoryAllocator? allocator)
9797
{
@@ -116,7 +116,17 @@ protected BaseExifReader(Stream stream, MemoryAllocator? allocator)
116116
/// </summary>
117117
public uint ThumbnailOffset { get; protected set; }
118118

119-
public bool IsBigEndian { get; protected set; }
119+
public bool IsBigEndian
120+
{
121+
get => this.isBigEndian;
122+
protected set
123+
{
124+
this.isBigEndian = value;
125+
this.ByteOrder = value ? ByteOrder.BigEndian : ByteOrder.LittleEndian;
126+
}
127+
}
128+
129+
protected ByteOrder ByteOrder { get; private set; }
120130

121131
public List<(ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif)> BigValues { get; } = new();
122132

@@ -485,7 +495,14 @@ private void ReadValue64(List<IExifValue> values, Span<byte> offsetBuffer)
485495

486496
private void Add(IList<IExifValue> values, IExifValue exif, object? value)
487497
{
488-
if (!exif.TrySetValue(value))
498+
if (exif is ExifEncodedString encodedString)
499+
{
500+
if (!encodedString.TrySetValue(value, this.ByteOrder))
501+
{
502+
return;
503+
}
504+
}
505+
else if (!exif.TrySetValue(value))
489506
{
490507
return;
491508
}

src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ public abstract partial class ExifTag : IEquatable<ExifTag>
2323
/// </summary>
2424
/// <param name="left">The first <see cref="ExifTag"/> to compare.</param>
2525
/// <param name="right"> The second <see cref="ExifTag"/> to compare.</param>
26-
public static bool operator ==(ExifTag left, ExifTag right) => Equals(left, right);
26+
public static bool operator ==(ExifTag? left, ExifTag? right) => left?.Equals(right) == true;
2727

2828
/// <summary>
2929
/// Determines whether the specified <see cref="ExifTag"/> instances are not considered equal.
3030
/// </summary>
3131
/// <param name="left">The first <see cref="ExifTag"/> to compare.</param>
3232
/// <param name="right"> The second <see cref="ExifTag"/> to compare.</param>
33-
public static bool operator !=(ExifTag left, ExifTag right) => !Equals(left, right);
33+
public static bool operator !=(ExifTag? left, ExifTag? right) => !(left == right);
3434

3535
/// <inheritdoc/>
3636
public override bool Equals(object? obj)

src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ private ExifEncodedString(ExifEncodedString value)
2424

2525
protected override string StringValue => this.Value.Text;
2626

27-
public override bool TrySetValue(object? value)
27+
public bool TrySetValue(object? value, ByteOrder order)
2828
{
2929
if (base.TrySetValue(value))
3030
{
@@ -38,7 +38,7 @@ public override bool TrySetValue(object? value)
3838
}
3939
else if (value is byte[] buffer)
4040
{
41-
if (ExifEncodedStringHelpers.TryParse(buffer, out EncodedString encodedString))
41+
if (ExifEncodedStringHelpers.TryParse(buffer, order, out EncodedString encodedString))
4242
{
4343
this.Value = encodedString;
4444
return true;
@@ -48,5 +48,8 @@ public override bool TrySetValue(object? value)
4848
return false;
4949
}
5050

51+
public override bool TrySetValue(object? value)
52+
=> this.TrySetValue(value, ByteOrder.LittleEndian);
53+
5154
public override IExifValue DeepClone() => new ExifEncodedString(this);
5255
}

tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using SixLabors.ImageSharp.Formats;
66
using SixLabors.ImageSharp.Formats.Webp;
77
using SixLabors.ImageSharp.Metadata;
8+
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
89
using SixLabors.ImageSharp.PixelFormats;
910
using SixLabors.ImageSharp.Tests.TestUtilities;
1011
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@@ -559,4 +560,22 @@ public void WebpDecoder_CanDecode_Issue2925<TPixel>(TestImageProvider<TPixel> pr
559560
image.DebugSave(provider);
560561
image.CompareToOriginal(provider, ReferenceDecoder);
561562
}
563+
564+
[Theory]
565+
[WithFile(Lossy.Issue2906, PixelTypes.Rgba32)]
566+
public void WebpDecoder_CanDecode_Issue2906<TPixel>(TestImageProvider<TPixel> provider)
567+
where TPixel : unmanaged, IPixel<TPixel>
568+
{
569+
using Image<TPixel> image = provider.GetImage(WebpDecoder.Instance);
570+
571+
ExifProfile exifProfile = image.Metadata.ExifProfile;
572+
IExifValue<EncodedString> comment = exifProfile.GetValue(ExifTag.UserComment);
573+
574+
Assert.NotNull(comment);
575+
Assert.Equal(EncodedString.CharacterCode.Unicode, comment.Value.Code);
576+
Assert.StartsWith("1girl, pariya, ", comment.Value.Text);
577+
578+
image.DebugSave(provider);
579+
image.CompareToOriginal(provider, ReferenceDecoder);
580+
}
562581
}

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,7 @@ public static class Lossy
860860
public const string Issue2801 = "Webp/issues/Issue2801.webp";
861861
public const string Issue2866 = "Webp/issues/Issue2866.webp";
862862
public const string Issue2925 = "Webp/issues/Issue2925.webp";
863+
public const string Issue2906 = "Webp/issues/Issue2906.webp";
863864
}
864865
}
865866

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:56fe6a91feb9545c0a15966e0f6bc560890b193073c96ae9e39bf387c7e0cbca
3+
size 157092

0 commit comments

Comments
 (0)