diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs index 398e06ee54..543e3d5c4d 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs @@ -2,21 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers.Binary; using System.Text; -using static SixLabors.ImageSharp.Metadata.Profiles.Exif.EncodedString; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { internal static class ExifConstants { - public const int CharacterCodeBytesLength = 8; - - private const ulong AsciiCode = 0x_41_53_43_49_49_00_00_00; - private const ulong JISCode = 0x_4A_49_53_00_00_00_00_00; - private const ulong UnicodeCode = 0x_55_4E_49_43_4F_44_45_00; - private const ulong UndefinedCode = 0x_00_00_00_00_00_00_00_00; - public static ReadOnlySpan LittleEndianByteOrderMarker => new byte[] { (byte)'I', @@ -35,61 +26,5 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif // UTF-8 is better than ASCII, UTF-8 encodes the ASCII codes the same way public static Encoding DefaultEncoding => Encoding.UTF8; - - public static Encoding JIS0208Encoding => Encoding.GetEncoding(932); - - private static ReadOnlySpan AsciiCodeBytes => new byte[] { 0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0 }; - - private static ReadOnlySpan JISCodeBytes => new byte[] { 0x4A, 0x49, 0x53, 0, 0, 0, 0, 0 }; - - private static ReadOnlySpan UnicodeCodeBytes => new byte[] { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0 }; - - private static ReadOnlySpan UndefinedCodeBytes => new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; - - public static bool TryDetect(ReadOnlySpan buffer, out CharacterCode code) - { - if (buffer.Length >= CharacterCodeBytesLength) - { - ulong test = BinaryPrimitives.ReadUInt64LittleEndian(buffer); - switch (test) - { - case AsciiCode: - code = CharacterCode.ASCII; - return true; - case JISCode: - code = CharacterCode.JIS; - return true; - case UnicodeCode: - code = CharacterCode.Unicode; - return true; - case UndefinedCode: - code = CharacterCode.Undefined; - return true; - default: - break; - } - } - - code = default; - return false; - } - - public static ReadOnlySpan GetCodeBytes(CharacterCode code) => code switch - { - CharacterCode.ASCII => AsciiCodeBytes, - CharacterCode.JIS => JISCodeBytes, - CharacterCode.Unicode => UnicodeCodeBytes, - CharacterCode.Undefined => UndefinedCodeBytes, - _ => UndefinedCodeBytes - }; - - public static Encoding GetEncoding(CharacterCode code) => code switch - { - CharacterCode.ASCII => Encoding.ASCII, - CharacterCode.JIS => JIS0208Encoding, - CharacterCode.Unicode => Encoding.Unicode, - CharacterCode.Undefined => Encoding.UTF8, - _ => Encoding.UTF8 - }; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs new file mode 100644 index 0000000000..c519fb23d7 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Text; +using static SixLabors.ImageSharp.Metadata.Profiles.Exif.EncodedString; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal static class ExifEncodedStringHelpers + { + public const int CharacterCodeBytesLength = 8; + + private const ulong AsciiCode = 0x_41_53_43_49_49_00_00_00; + private const ulong JISCode = 0x_4A_49_53_00_00_00_00_00; + private const ulong UnicodeCode = 0x_55_4E_49_43_4F_44_45_00; + private const ulong UndefinedCode = 0x_00_00_00_00_00_00_00_00; + + private static ReadOnlySpan AsciiCodeBytes => new byte[] { 0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0 }; + + private static ReadOnlySpan JISCodeBytes => new byte[] { 0x4A, 0x49, 0x53, 0, 0, 0, 0, 0 }; + + private static ReadOnlySpan UnicodeCodeBytes => new byte[] { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0 }; + + private static ReadOnlySpan UndefinedCodeBytes => new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; + + private static Encoding JIS0208Encoding => Encoding.GetEncoding(932); + + private static bool TryDetect(ReadOnlySpan buffer, out CharacterCode code) + { + if (buffer.Length >= CharacterCodeBytesLength) + { + ulong test = BinaryPrimitives.ReadUInt64LittleEndian(buffer); + switch (test) + { + case AsciiCode: + code = CharacterCode.ASCII; + return true; + case JISCode: + code = CharacterCode.JIS; + return true; + case UnicodeCode: + code = CharacterCode.Unicode; + return true; + case UndefinedCode: + code = CharacterCode.Undefined; + return true; + default: + break; + } + } + + code = default; + return false; + } + + public static ReadOnlySpan GetCodeBytes(CharacterCode code) => code switch + { + CharacterCode.ASCII => AsciiCodeBytes, + CharacterCode.JIS => JISCodeBytes, + CharacterCode.Unicode => UnicodeCodeBytes, + CharacterCode.Undefined => UndefinedCodeBytes, + _ => UndefinedCodeBytes + }; + + public static Encoding GetEncoding(CharacterCode code) => code switch + { + CharacterCode.ASCII => Encoding.ASCII, + CharacterCode.JIS => JIS0208Encoding, + CharacterCode.Unicode => Encoding.Unicode, + CharacterCode.Undefined => Encoding.UTF8, + _ => Encoding.UTF8 + }; + + public static bool TryCreate(ReadOnlySpan buffer, out EncodedString encodedString) + { + if (TryDetect(buffer, out CharacterCode code)) + { + string text = GetEncoding(code).GetString(buffer.Slice(CharacterCodeBytesLength)); + encodedString = new EncodedString(code, text); + return true; + } + + encodedString = default; + return false; + } + + public static uint GetDataLength(EncodedString encodedString) => + (uint)GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + CharacterCodeBytesLength; + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index eb7bb0675d..f50ecbad88 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -220,6 +220,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif if (this.TryReadSpan(buffer)) { object value = this.ConvertValue(tag.DataType, buffer, tag.NumberOfComponents > 1 || tag.Exif.IsArray); + if (value is EncodedString) + { + // Console.WriteLine("EncodedString tag: " + (ushort)tag.Exif.Tag); + } + this.Add(values, tag.Exif, value); } } @@ -269,12 +274,15 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifDataType.Ascii: return this.ConvertToString(buffer); case ExifDataType.Byte: + { if (!isArray) { return this.ConvertToByte(buffer); } - return buffer.ToArray(); + return ExifEncodedStringHelpers.TryCreate(buffer, out EncodedString encodedString) ? encodedString : buffer.ToArray(); + } + case ExifDataType.DoubleFloat: if (!isArray) { @@ -355,19 +363,15 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return ToArray(dataType, buffer, this.ConvertToUInt64); case ExifDataType.Undefined: + { if (!isArray) { return this.ConvertToByte(buffer); } - // ext processing - if (ExifConstants.TryDetect(buffer, out EncodedString.CharacterCode code)) - { - string text = ExifConstants.GetEncoding(code).GetString(buffer.Slice(ExifConstants.CharacterCodeBytesLength)); - return new EncodedString(code, text); - } + return ExifEncodedStringHelpers.TryCreate(buffer, out EncodedString encodedString) ? encodedString : buffer.ToArray(); + } - return buffer.ToArray(); default: throw new NotSupportedException($"Data type {dataType} is not supported."); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index 4150a9a50a..dfd7a599b0 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -281,7 +281,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif if (value is EncodedString encodedString) { - return (uint)ExifConstants.GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + 8; + return ExifEncodedStringHelpers.GetDataLength(encodedString); } if (value is Array arrayValue) @@ -387,11 +387,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifDataType.Undefined: if (value is EncodedString encodedString) { - ReadOnlySpan codeBytes = ExifConstants.GetCodeBytes(encodedString.Code); + ReadOnlySpan codeBytes = ExifEncodedStringHelpers.GetCodeBytes(encodedString.Code); codeBytes.CopyTo(destination.Slice(offset)); offset += codeBytes.Length; - ReadOnlySpan dataBytes = ExifConstants.GetEncoding(encodedString.Code).GetBytes(encodedString.Text); + ReadOnlySpan dataBytes = ExifEncodedStringHelpers.GetEncoding(encodedString.Code).GetBytes(encodedString.Text); dataBytes.CopyTo(destination.Slice(offset)); offset += dataBytes.Length;