Browse Source

implement read&write encoded string tags

pull/1935/head
Ildar Khayrutdinov 5 years ago
parent
commit
e78190f647
  1. 64
      src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs
  2. 9
      src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
  3. 36
      src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs
  4. 52
      src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs
  5. 31
      src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedStringCode.cs
  6. 48
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifEncodedString.cs
  7. 32
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

64
src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs

@ -2,11 +2,25 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Text;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
internal static class ExifConstants
{
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 readonly byte[] AsciiCodeBytes = { 0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0 };
private static readonly byte[] JISCodeBytes = { 0x4A, 0x49, 0x53, 0, 0, 0, 0, 0 };
private static readonly byte[] UnicodeCodeBytes = { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0 };
private static readonly byte[] UndefinedCodeBytes = { 0, 0, 0, 0, 0, 0, 0, 0 };
public const int CharacterCodeBytesLength = 8;
public static ReadOnlySpan<byte> LittleEndianByteOrderMarker => new byte[]
{
(byte)'I',
@ -22,5 +36,55 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
0x00,
0x2A
};
public static Encoding DefaultAsciiEncoding => Encoding.UTF8;
public static Encoding JIS0208Encoding => Encoding.GetEncoding(932);
public static bool TryDetect(ReadOnlySpan<byte> buffer, out EncodedStringCode code)
{
if (buffer.Length >= CharacterCodeBytesLength)
{
ulong test = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
switch (test)
{
case AsciiCode:
code = EncodedStringCode.ASCII;
return true;
case JISCode:
code = EncodedStringCode.JIS;
return true;
case UnicodeCode:
code = EncodedStringCode.Unicode;
return true;
case UndefinedCode:
code = EncodedStringCode.Undefined;
return true;
default:
break;
}
}
code = default;
return false;
}
public static ReadOnlySpan<byte> GetCodeBytes(EncodedStringCode code) => code switch
{
EncodedStringCode.ASCII => AsciiCodeBytes,
EncodedStringCode.JIS => JISCodeBytes,
EncodedStringCode.Unicode => UnicodeCodeBytes,
EncodedStringCode.Undefined => UndefinedCodeBytes,
_ => UndefinedCodeBytes
};
public static Encoding GetEncoding(EncodedStringCode code) => code switch
{
EncodedStringCode.ASCII => Encoding.ASCII,
EncodedStringCode.JIS => JIS0208Encoding,
EncodedStringCode.Unicode => Encoding.Unicode,
EncodedStringCode.Undefined => Encoding.UTF8,
_ => Encoding.UTF8
};
}
}

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

@ -252,7 +252,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
buffer = buffer.Slice(0, nullCharIndex);
}
return Encoding.UTF8.GetString(buffer);
return ExifConstants.DefaultAsciiEncoding.GetString(buffer);
}
private object ConvertValue(ExifDataType dataType, ReadOnlySpan<byte> buffer, bool isArray)
@ -360,6 +360,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
return this.ConvertToByte(buffer);
}
// ext processing
if (ExifConstants.TryDetect(buffer, out EncodedStringCode code))
{
string text = ExifConstants.GetEncoding(code).GetString(buffer.Slice(ExifConstants.CharacterCodeBytesLength));
return new EncodedString(text, code);
}
return buffer.ToArray();
default:
throw new NotSupportedException($"Data type {dataType} is not supported.");

36
src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs

@ -276,7 +276,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
if (exifValue.DataType == ExifDataType.Ascii)
{
return (uint)Encoding.UTF8.GetBytes((string)value).Length + 1;
return (uint)ExifConstants.DefaultAsciiEncoding.GetByteCount((string)value) + 1;
}
if (value is EncodedString encodedString)
{
return (uint)ExifConstants.GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + 8;
}
if (value is Array arrayValue)
@ -289,11 +294,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
private static int WriteArray(IExifValue value, Span<byte> destination, int offset)
{
if (value.DataType == ExifDataType.Ascii)
{
return WriteValue(ExifDataType.Ascii, value.GetValue(), destination, offset);
}
int newOffset = offset;
foreach (object obj in (Array)value.GetValue())
{
@ -378,13 +378,31 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
switch (dataType)
{
case ExifDataType.Ascii:
offset = Write(Encoding.UTF8.GetBytes((string)value), destination, offset);
offset = Write(ExifConstants.DefaultAsciiEncoding.GetBytes((string)value), destination, offset);
destination[offset] = 0;
return offset + 1;
case ExifDataType.Byte:
case ExifDataType.Undefined:
destination[offset] = (byte)value;
return offset + 1;
case ExifDataType.Undefined:
if (value is EncodedString encodedString)
{
ReadOnlySpan<byte> codeBytes = ExifConstants.GetCodeBytes(encodedString.Code);
codeBytes.CopyTo(destination.Slice(offset));
offset += codeBytes.Length;
ReadOnlySpan<byte> dataBytes = ExifConstants.GetEncoding(encodedString.Code).GetBytes(encodedString.Text);
dataBytes.CopyTo(destination.Slice(offset));
offset += dataBytes.Length;
return offset;
}
else
{
destination[offset] = (byte)value;
return offset + 1;
}
case ExifDataType.DoubleFloat:
return WriteDouble((double)value, destination, offset);
case ExifDataType.Short:
@ -427,7 +445,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
internal static int WriteValue(IExifValue value, Span<byte> destination, int offset)
{
if (value.IsArray && value.DataType != ExifDataType.Ascii)
if (value.IsArray)
{
return WriteArray(value, destination, offset);
}

52
src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedString.cs

@ -0,0 +1,52 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
public readonly struct EncodedString : IEquatable<EncodedString>
{
/// <summary>
/// Initializes a new instance of the <see cref="EncodedString" /> struct.
/// </summary>
/// <param name="text">The text.</param>
public EncodedString(string text)
: this(text, EncodedStringCode.Unicode)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EncodedString" /> struct.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="code">The code.</param>
public EncodedString(string text, EncodedStringCode code)
{
this.Text = text;
this.Code = code;
}
/// <summary>
/// Gets the text.
/// </summary>
public string Text { get; }
/// <summary>
/// Gets the character ode.
/// </summary>
public EncodedStringCode Code { get; }
/// <inheritdoc/>
public override bool Equals(object obj) => obj is EncodedString other && this.Equals(other);
/// <inheritdoc/>
public bool Equals(EncodedString other)
{
return this.Text == other.Text && this.Code == other.Code;
}
/// <inheritdoc/>
public override string ToString() => this.Text;
}
}

31
src/ImageSharp/Metadata/Profiles/Exif/Values/EncodedStringCode.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
/// <summary>
/// The 8-byte The character code enum.
/// </summary>
public enum EncodedStringCode
{
/// <summary>
/// The ASCII ITU-T T.50 IA5 character code.
/// </summary>
ASCII,
/// <summary>
/// The JIS X208-1990 character code.
/// </summary>
JIS,
/// <summary>
/// The Unicode character code.
/// </summary>
Unicode,
/// <summary>
/// The undefined character code.
/// </summary>
Undefined
}
}

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

@ -0,0 +1,48 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Globalization;
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
{
internal sealed class ExifEncodedString : ExifValue<EncodedString>
{
public ExifEncodedString(ExifTag<EncodedString> tag)
: base(tag)
{
}
public ExifEncodedString(ExifTagValue tag)
: base(tag)
{
}
private ExifEncodedString(ExifEncodedString value)
: base(value)
{
}
public override ExifDataType DataType => ExifDataType.Undefined;
protected override string StringValue => this.Value.Text;
public override bool TrySetValue(object value)
{
if (base.TrySetValue(value))
{
return true;
}
switch (value)
{
case string stringValue:
this.Value = new EncodedString(stringValue);
return true;
default:
return false;
}
}
public override IExifValue DeepClone() => new ExifEncodedString(this);
}
}

32
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata;
@ -287,5 +288,36 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(72, imageInfo.Metadata.HorizontalResolution);
Assert.Equal(72, imageInfo.Metadata.VerticalResolution);
});
[Fact]
public void ExifIfdStructure()
{
byte[] exifBytes;
using var memoryStream = new MemoryStream();
using (var image = Image.Load(TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora)))
{
var exif = new ExifProfile();
exif.SetValue<byte[]>(ExifTag.XPAuthor, Encoding.GetEncoding("UCS-2").GetBytes("Dan Petitt"));
exif.SetValue<byte[]>(ExifTag.XPTitle, Encoding.GetEncoding("UCS-2").GetBytes("A bit of test metadata for image title"));
exif.SetValue<byte[]>(ExifTag.UserComment, Encoding.ASCII.GetBytes("A bit of normal comment text"));
exif.SetValue(ExifTag.GPSDateStamp, "2022-01-06");
exif.SetValue(ExifTag.XPKeywords, new byte[] { 0x41, 0x53, 0x43, 0x49, 0x49, 00, 00, 00, 0x41, 0x41, 0x41 });
image.Metadata.ExifProfile = exif;
exifBytes = exif.ToByteArray();
image.Save("c:\\temp\\1.jpeg");
image.Save(memoryStream, new JpegEncoder());
}
memoryStream.Seek(0, SeekOrigin.Begin);
using (var image = Image.Load(memoryStream))
{
Assert.NotNull(image.Metadata.ExifProfile);
}
}
}
}

Loading…
Cancel
Save