Skip to content

Commit cda6b24

Browse files
Bug fixes of reading and writing (UCS2 and EncodedString)
1 parent 14d7b60 commit cda6b24

File tree

14 files changed

+233
-115
lines changed

14 files changed

+233
-115
lines changed

src/ImageSharp/ImageSharp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949

5050
<ItemGroup>
5151
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
52+
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
5253
</ItemGroup>
5354

5455
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) OR '$(TargetFramework)' == 'net472'">

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

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ internal static class ExifEncodedStringHelpers
1212
{
1313
public const int CharacterCodeBytesLength = 8;
1414

15-
private const ulong AsciiCode = 0x_41_53_43_49_49_00_00_00;
16-
private const ulong JISCode = 0x_4A_49_53_00_00_00_00_00;
17-
private const ulong UnicodeCode = 0x_55_4E_49_43_4F_44_45_00;
15+
private const ulong AsciiCode = 0x_00_00_00_49_49_43_53_41;
16+
private const ulong JISCode = 0x_00_00_00_00_00_53_49_4A;
17+
private const ulong UnicodeCode = 0x_45_44_4F_43_49_4E_55;
1818
private const ulong UndefinedCode = 0x_00_00_00_00_00_00_00_00;
1919

2020
private static ReadOnlySpan<byte> AsciiCodeBytes => new byte[] { 0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0 };
@@ -25,7 +25,13 @@ internal static class ExifEncodedStringHelpers
2525

2626
private static ReadOnlySpan<byte> UndefinedCodeBytes => new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
2727

28-
private static Encoding JIS0208Encoding => Encoding.GetEncoding(932);
28+
private static Encoding JIS0208Encoding => CodePagesEncodingProvider.Instance.GetEncoding(932);
29+
30+
public static bool IsEncodedString(ExifTagValue tag) => tag switch
31+
{
32+
ExifTagValue.UserComment or ExifTagValue.GPSProcessingMethod or ExifTagValue.GPSAreaInformation => true,
33+
_ => false
34+
};
2935

3036
public static ReadOnlySpan<byte> GetCodeBytes(CharacterCode code) => code switch
3137
{
@@ -45,7 +51,7 @@ internal static class ExifEncodedStringHelpers
4551
_ => Encoding.UTF8
4652
};
4753

48-
public static bool TryCreate(ReadOnlySpan<byte> buffer, out EncodedString encodedString)
54+
public static bool TryParse(ReadOnlySpan<byte> buffer, out EncodedString encodedString)
4955
{
5056
if (TryDetect(buffer, out CharacterCode code))
5157
{
@@ -61,17 +67,17 @@ public static bool TryCreate(ReadOnlySpan<byte> buffer, out EncodedString encode
6167
public static uint GetDataLength(EncodedString encodedString) =>
6268
(uint)GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + CharacterCodeBytesLength;
6369

64-
public static bool IsEncodedString(ExifTagValue tag)
70+
public static byte[] GetData(EncodedString encodedString)
6571
{
66-
switch (tag)
67-
{
68-
case ExifTagValue.UserComment:
69-
case ExifTagValue.GPSProcessingMethod:
70-
case ExifTagValue.GPSAreaInformation:
71-
return true;
72-
default:
73-
return false;
74-
}
72+
int length = (int)GetDataLength(encodedString);
73+
byte[] buffer = new byte[length];
74+
75+
GetCodeBytes(encodedString.Code).CopyTo(buffer);
76+
77+
string text = encodedString.Text;
78+
GetEncoding(encodedString.Code).GetBytes(text, 0, text.Length, buffer, CharacterCodeBytesLength);
79+
80+
return buffer;
7581
}
7682

7783
private static bool TryDetect(ReadOnlySpan<byte> buffer, out CharacterCode code)

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

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -220,10 +220,6 @@ protected void ReadBigValue(IList<IExifValue> values, (ulong Offset, ExifDataTyp
220220
if (this.TryReadSpan(buffer))
221221
{
222222
object value = this.ConvertValue(tag.Exif, tag.DataType, buffer, tag.NumberOfComponents > 1 || tag.Exif.IsArray);
223-
if (value is EncodedString)
224-
{
225-
// Console.WriteLine("EncodedString tag: " + (ushort)tag.Exif.Tag);
226-
}
227223

228224
this.Add(values, tag.Exif, value);
229225
}
@@ -248,7 +244,7 @@ private static TDataType[] ToArray<TDataType>(ExifDataType dataType, ReadOnlySpa
248244

249245
private byte ConvertToByte(ReadOnlySpan<byte> buffer) => buffer[0];
250246

251-
private string ConvertToString(ReadOnlySpan<byte> buffer)
247+
private string ConvertToString(Encoding encoding, ReadOnlySpan<byte> buffer)
252248
{
253249
int nullCharIndex = buffer.IndexOf((byte)0);
254250

@@ -257,7 +253,7 @@ private string ConvertToString(ReadOnlySpan<byte> buffer)
257253
buffer = buffer.Slice(0, nullCharIndex);
258254
}
259255

260-
return ExifConstants.DefaultEncoding.GetString(buffer);
256+
return encoding.GetString(buffer);
261257
}
262258

263259
private object ConvertValue(ExifValue exifValue, ExifDataType dataType, ReadOnlySpan<byte> buffer, bool isArray)
@@ -267,35 +263,20 @@ private object ConvertValue(ExifValue exifValue, ExifDataType dataType, ReadOnly
267263
return null;
268264
}
269265

270-
var tagValue = (ExifTagValue)(ushort)exifValue.Tag;
271-
if (ExifUcs2StringHelpers.IsUcs2Tag(tagValue))
272-
{
273-
return ExifUcs2StringHelpers.ConvertToString(buffer);
274-
}
275-
else if (ExifEncodedStringHelpers.IsEncodedString(tagValue))
276-
{
277-
if (ExifEncodedStringHelpers.TryCreate(buffer, out EncodedString encodedString))
278-
{
279-
return encodedString;
280-
}
281-
}
282-
283266
switch (dataType)
284267
{
285268
case ExifDataType.Unknown:
286269
return null;
287270
case ExifDataType.Ascii:
288-
return this.ConvertToString(buffer);
271+
return this.ConvertToString(ExifConstants.DefaultEncoding, buffer);
289272
case ExifDataType.Byte:
290273
case ExifDataType.Undefined:
291-
{
292274
if (!isArray)
293275
{
294276
return this.ConvertToByte(buffer);
295277
}
296278

297-
return ExifEncodedStringHelpers.TryCreate(buffer, out EncodedString encodedString) ? encodedString : buffer.ToArray();
298-
}
279+
return buffer.ToArray();
299280

300281
case ExifDataType.DoubleFloat:
301282
if (!isArray)

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

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,10 @@ internal static class ExifUcs2StringHelpers
1010
{
1111
public static Encoding Ucs2Encoding => Encoding.GetEncoding("UCS-2");
1212

13-
public static bool IsUcs2Tag(ExifTagValue tag)
13+
public static bool IsUcs2Tag(ExifTagValue tag) => tag switch
1414
{
15-
switch (tag)
16-
{
17-
case ExifTagValue.XPAuthor:
18-
case ExifTagValue.XPComment:
19-
case ExifTagValue.XPKeywords:
20-
case ExifTagValue.XPSubject:
21-
case ExifTagValue.XPTitle:
22-
return true;
23-
default:
24-
return false;
25-
}
26-
}
27-
28-
public static string ConvertToString(ReadOnlySpan<byte> buffer) => Ucs2Encoding.GetString(buffer);
15+
ExifTagValue.XPAuthor or ExifTagValue.XPComment or ExifTagValue.XPKeywords or ExifTagValue.XPSubject or ExifTagValue.XPTitle => true,
16+
_ => false,
17+
};
2918
}
3019
}

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

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -274,16 +274,21 @@ internal static uint GetNumberOfComponents(IExifValue exifValue)
274274
{
275275
object value = exifValue.GetValue();
276276

277-
if (exifValue.DataType == ExifDataType.Ascii)
277+
if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag))
278278
{
279-
return (uint)(ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag) ? ExifUcs2StringHelpers.Ucs2Encoding : ExifConstants.DefaultEncoding).GetByteCount((string)value) + 1;
279+
return (uint)ExifUcs2StringHelpers.Ucs2Encoding.GetByteCount((string)value);
280280
}
281281

282282
if (value is EncodedString encodedString)
283283
{
284284
return ExifEncodedStringHelpers.GetDataLength(encodedString);
285285
}
286286

287+
if (exifValue.DataType == ExifDataType.Ascii)
288+
{
289+
return (uint)ExifConstants.DefaultEncoding.GetByteCount((string)value) + 1;
290+
}
291+
287292
if (value is Array arrayValue)
288293
{
289294
return (uint)arrayValue.Length;
@@ -378,21 +383,14 @@ private static int WriteValue(IExifValue exifValue, ExifDataType dataType, objec
378383
switch (dataType)
379384
{
380385
case ExifDataType.Ascii:
381-
offset = Write((ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag) ? ExifUcs2StringHelpers.Ucs2Encoding : ExifConstants.DefaultEncoding).GetBytes((string)value), destination, offset);
386+
offset = Write(ExifConstants.DefaultEncoding.GetBytes((string)value), destination, offset);
382387
destination[offset] = 0;
383388
return offset + 1;
384389
case ExifDataType.Byte:
385390
case ExifDataType.Undefined:
386-
if (value is EncodedString encodedString)
391+
if (value is byte[] array)
387392
{
388-
ReadOnlySpan<byte> codeBytes = ExifEncodedStringHelpers.GetCodeBytes(encodedString.Code);
389-
codeBytes.CopyTo(destination.Slice(offset));
390-
offset += codeBytes.Length;
391-
392-
ReadOnlySpan<byte> dataBytes = ExifEncodedStringHelpers.GetEncoding(encodedString.Code).GetBytes(encodedString.Text);
393-
dataBytes.CopyTo(destination.Slice(offset));
394-
offset += dataBytes.Length;
395-
393+
offset = Write(array, destination, offset);
396394
return offset;
397395
}
398396
else
@@ -441,14 +439,25 @@ private static int WriteValue(IExifValue exifValue, ExifDataType dataType, objec
441439
}
442440
}
443441

444-
internal static int WriteValue(IExifValue value, Span<byte> destination, int offset)
442+
internal static int WriteValue(IExifValue exifValue, Span<byte> destination, int offset)
445443
{
446-
if (value.IsArray)
444+
object value = exifValue.GetValue();
445+
446+
if (ExifUcs2StringHelpers.IsUcs2Tag((ExifTagValue)(ushort)exifValue.Tag))
447+
{
448+
value = ExifUcs2StringHelpers.Ucs2Encoding.GetBytes((string)value);
449+
}
450+
else if (value is EncodedString encodedString)
451+
{
452+
value = ExifEncodedStringHelpers.GetData(encodedString);
453+
}
454+
455+
if (exifValue.IsArray)
447456
{
448-
return WriteArray(value, destination, offset);
457+
return WriteArray(exifValue, destination, offset);
449458
}
450459

451-
return WriteValue(value, value.DataType, value.GetValue(), destination, offset);
460+
return WriteValue(exifValue, exifValue.DataType, value, destination, offset);
452461
}
453462
}
454463
}

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

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -275,30 +275,5 @@ public abstract partial class ExifTag
275275
/// Gets the GPSDateStamp exif tag.
276276
/// </summary>
277277
public static ExifTag<string> GPSDateStamp { get; } = new ExifTag<string>(ExifTagValue.GPSDateStamp);
278-
279-
/// <summary>
280-
/// Gets the title tag used by Windows (encoded in UCS2).
281-
/// </summary>
282-
public static ExifTag<string> XPTitle => new ExifTag<string>(ExifTagValue.XPTitle);
283-
284-
/// <summary>
285-
/// Gets the comment tag used by Windows (encoded in UCS2).
286-
/// </summary>
287-
public static ExifTag<string> XPComment => new ExifTag<string>(ExifTagValue.XPComment);
288-
289-
/// <summary>
290-
/// Gets the author tag used by Windows (encoded in UCS2).
291-
/// </summary>
292-
public static ExifTag<string> XPAuthor => new ExifTag<string>(ExifTagValue.XPAuthor);
293-
294-
/// <summary>
295-
/// Gets the keywords tag used by Windows (encoded in UCS2).
296-
/// </summary>
297-
public static ExifTag<string> XPKeywords => new ExifTag<string>(ExifTagValue.XPKeywords);
298-
299-
/// <summary>
300-
/// Gets the subject tag used by Windows (encoded in UCS2).
301-
/// </summary>
302-
public static ExifTag<string> XPSubject => new ExifTag<string>(ExifTagValue.XPSubject);
303278
}
304279
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
5+
{
6+
/// <content/>
7+
public abstract partial class ExifTag
8+
{
9+
/// <summary>
10+
/// Gets the title tag used by Windows (encoded in UCS2).
11+
/// </summary>
12+
public static ExifTag<string> XPTitle => new ExifTag<string>(ExifTagValue.XPTitle);
13+
14+
/// <summary>
15+
/// Gets the comment tag used by Windows (encoded in UCS2).
16+
/// </summary>
17+
public static ExifTag<string> XPComment => new ExifTag<string>(ExifTagValue.XPComment);
18+
19+
/// <summary>
20+
/// Gets the author tag used by Windows (encoded in UCS2).
21+
/// </summary>
22+
public static ExifTag<string> XPAuthor => new ExifTag<string>(ExifTagValue.XPAuthor);
23+
24+
/// <summary>
25+
/// Gets the keywords tag used by Windows (encoded in UCS2).
26+
/// </summary>
27+
public static ExifTag<string> XPKeywords => new ExifTag<string>(ExifTagValue.XPKeywords);
28+
29+
/// <summary>
30+
/// Gets the subject tag used by Windows (encoded in UCS2).
31+
/// </summary>
32+
public static ExifTag<string> XPSubject => new ExifTag<string>(ExifTagValue.XPSubject);
33+
}
34+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ public enum CharacterCode
6666
/// </summary>
6767
public string Text { get; }
6868

69+
/// <summary>
70+
/// Converts the specified <see cref="string"/> to an instance of this type.
71+
/// </summary>
72+
/// <param name="text">The text value.</param>
73+
public static implicit operator EncodedString(string text) => new(text);
74+
75+
/// <summary>
76+
/// Converts the specified <see cref="EncodedString"/> to a <see cref="string"/>.
77+
/// </summary>
78+
/// <param name="encodedString">The <see cref="EncodedString"/> to convert.</param>
79+
public static explicit operator string(EncodedString encodedString) => encodedString.Text;
80+
6981
/// <inheritdoc/>
7082
public override bool Equals(object obj) => obj is EncodedString other && this.Equals(other);
7183

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ public override bool TrySetValue(object value)
3838
this.Value = new EncodedString(stringValue);
3939
return true;
4040
}
41+
else if (value is byte[] buffer)
42+
{
43+
if (ExifEncodedStringHelpers.TryParse(buffer, out EncodedString encodedString))
44+
{
45+
this.Value = encodedString;
46+
return true;
47+
}
48+
}
4149

4250
return false;
4351
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
5+
{
6+
internal sealed class ExifUcs2String : ExifValue<string>
7+
{
8+
public ExifUcs2String(ExifTag<string> tag)
9+
: base(tag)
10+
{
11+
}
12+
13+
public ExifUcs2String(ExifTagValue tag)
14+
: base(tag)
15+
{
16+
}
17+
18+
private ExifUcs2String(ExifUcs2String value)
19+
: base(value)
20+
{
21+
}
22+
23+
public override ExifDataType DataType => ExifDataType.Byte;
24+
25+
protected override string StringValue => this.Value;
26+
27+
public override object GetValue() => this.Value;
28+
29+
public override bool TrySetValue(object value)
30+
{
31+
if (base.TrySetValue(value))
32+
{
33+
return true;
34+
}
35+
36+
if (value is byte[] buffer)
37+
{
38+
this.Value = ExifUcs2StringHelpers.Ucs2Encoding.GetString(buffer);
39+
return true;
40+
}
41+
42+
return false;
43+
}
44+
45+
public override IExifValue DeepClone() => new ExifUcs2String(this);
46+
}
47+
}

0 commit comments

Comments
 (0)