diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs index e9f46731c8..9833e4e00a 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs @@ -50,22 +50,51 @@ private static Encoding JIS0208Encoding _ => UndefinedCodeBytes }; - public static Encoding GetEncoding(CharacterCode code) => code switch + public static Encoding GetEncoding(CharacterCode code, ByteOrder order) => code switch { CharacterCode.ASCII => Encoding.ASCII, CharacterCode.JIS => JIS0208Encoding, - CharacterCode.Unicode => Encoding.Unicode, + CharacterCode.Unicode => order is ByteOrder.BigEndian ? Encoding.BigEndianUnicode : Encoding.Unicode, CharacterCode.Undefined => Encoding.UTF8, _ => Encoding.UTF8 }; - public static bool TryParse(ReadOnlySpan buffer, out EncodedString encodedString) + public static bool TryParse(ReadOnlySpan buffer, ByteOrder order, out EncodedString encodedString) { if (TryDetect(buffer, out CharacterCode code)) { - string text = GetEncoding(code).GetString(buffer[CharacterCodeBytesLength..]); - encodedString = new EncodedString(code, text); - return true; + ReadOnlySpan textBuffer = buffer[CharacterCodeBytesLength..]; + if (code == CharacterCode.Unicode && textBuffer.Length >= 2) + { + // Check BOM + if (textBuffer[0] == 0xFF && textBuffer[1] == 0xFE) + { + // Little-endian BOM + string text = Encoding.Unicode.GetString(textBuffer[2..]); + encodedString = new EncodedString(code, text); + return true; + } + else if (textBuffer[0] == 0xFE && textBuffer[1] == 0xFF) + { + // Big-endian BOM + string text = Encoding.BigEndianUnicode.GetString(textBuffer[2..]); + encodedString = new EncodedString(code, text); + return true; + } + else + { + // No BOM, use EXIF byte order + string text = GetEncoding(code, order).GetString(textBuffer); + encodedString = new EncodedString(code, text); + return true; + } + } + else + { + string text = GetEncoding(code, order).GetString(textBuffer); + encodedString = new EncodedString(code, text); + return true; + } } encodedString = default; @@ -73,14 +102,14 @@ public static bool TryParse(ReadOnlySpan buffer, out EncodedString encoded } public static uint GetDataLength(EncodedString encodedString) => - (uint)GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + CharacterCodeBytesLength; + (uint)GetEncoding(encodedString.Code, ByteOrder.LittleEndian).GetByteCount(encodedString.Text) + CharacterCodeBytesLength; public static int Write(EncodedString encodedString, Span destination) { GetCodeBytes(encodedString.Code).CopyTo(destination); string text = encodedString.Text; - int count = Write(GetEncoding(encodedString.Code), text, destination[CharacterCodeBytesLength..]); + int count = Write(GetEncoding(encodedString.Code, ByteOrder.LittleEndian), text, destination[CharacterCodeBytesLength..]); return CharacterCodeBytesLength + count; } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index fe510f250d..d0dcaea9d5 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -90,8 +90,8 @@ internal abstract class BaseExifReader private readonly MemoryAllocator? allocator; private readonly Stream data; private List? invalidTags; - private List? subIfds; + private bool isBigEndian; protected BaseExifReader(Stream stream, MemoryAllocator? allocator) { @@ -116,7 +116,17 @@ protected BaseExifReader(Stream stream, MemoryAllocator? allocator) /// public uint ThumbnailOffset { get; protected set; } - public bool IsBigEndian { get; protected set; } + public bool IsBigEndian + { + get => this.isBigEndian; + protected set + { + this.isBigEndian = value; + this.ByteOrder = value ? ByteOrder.BigEndian : ByteOrder.LittleEndian; + } + } + + protected ByteOrder ByteOrder { get; private set; } public List<(ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif)> BigValues { get; } = new(); @@ -485,7 +495,14 @@ private void ReadValue64(List values, Span offsetBuffer) private void Add(IList values, IExifValue exif, object? value) { - if (!exif.TrySetValue(value)) + if (exif is ExifEncodedString encodedString) + { + if (!encodedString.TrySetValue(value, this.ByteOrder)) + { + return; + } + } + else if (!exif.TrySetValue(value)) { return; } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs index ea0b8060d5..8ffc2bb738 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.cs @@ -23,14 +23,14 @@ public abstract partial class ExifTag : IEquatable /// /// The first to compare. /// The second to compare. - public static bool operator ==(ExifTag left, ExifTag right) => Equals(left, right); + public static bool operator ==(ExifTag? left, ExifTag? right) => left?.Equals(right) == true; /// /// Determines whether the specified instances are not considered equal. /// /// The first to compare. /// The second to compare. - public static bool operator !=(ExifTag left, ExifTag right) => !Equals(left, right); + public static bool operator !=(ExifTag? left, ExifTag? right) => !(left == right); /// public override bool Equals(object? obj) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs index cce7cf3e89..14b097f816 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs @@ -24,7 +24,7 @@ private ExifEncodedString(ExifEncodedString value) protected override string StringValue => this.Value.Text; - public override bool TrySetValue(object? value) + public bool TrySetValue(object? value, ByteOrder order) { if (base.TrySetValue(value)) { @@ -38,7 +38,7 @@ public override bool TrySetValue(object? value) } else if (value is byte[] buffer) { - if (ExifEncodedStringHelpers.TryParse(buffer, out EncodedString encodedString)) + if (ExifEncodedStringHelpers.TryParse(buffer, order, out EncodedString encodedString)) { this.Value = encodedString; return true; @@ -48,5 +48,8 @@ public override bool TrySetValue(object? value) return false; } + public override bool TrySetValue(object? value) + => this.TrySetValue(value, ByteOrder.LittleEndian); + public override IExifValue DeepClone() => new ExifEncodedString(this); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index d81cd20849..c8d780cc20 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -5,6 +5,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -559,4 +560,22 @@ public void WebpDecoder_CanDecode_Issue2925(TestImageProvider pr image.DebugSave(provider); image.CompareToOriginal(provider, ReferenceDecoder); } + + [Theory] + [WithFile(Lossy.Issue2906, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Issue2906(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder.Instance); + + ExifProfile exifProfile = image.Metadata.ExifProfile; + IExifValue comment = exifProfile.GetValue(ExifTag.UserComment); + + Assert.NotNull(comment); + Assert.Equal(EncodedString.CharacterCode.Unicode, comment.Value.Code); + Assert.StartsWith("1girl, pariya, ", comment.Value.Text); + + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 6021de15cc..e75e7fa482 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -860,6 +860,7 @@ public static class Lossy public const string Issue2801 = "Webp/issues/Issue2801.webp"; public const string Issue2866 = "Webp/issues/Issue2866.webp"; public const string Issue2925 = "Webp/issues/Issue2925.webp"; + public const string Issue2906 = "Webp/issues/Issue2906.webp"; } } diff --git a/tests/Images/Input/Webp/issues/Issue2906.webp b/tests/Images/Input/Webp/issues/Issue2906.webp new file mode 100644 index 0000000000..0911da0472 --- /dev/null +++ b/tests/Images/Input/Webp/issues/Issue2906.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56fe6a91feb9545c0a15966e0f6bc560890b193073c96ae9e39bf387c7e0cbca +size 157092