Skip to content

Commit 1d18df9

Browse files
Merge branch 'main' into js/transform-metadata
2 parents 668e223 + 0c13a36 commit 1d18df9

28 files changed

+458
-683
lines changed

src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public EntryReader(Stream stream, ByteOrder byteOrder, MemoryAllocator allocator
1414
: base(stream, allocator) =>
1515
this.IsBigEndian = byteOrder == ByteOrder.BigEndian;
1616

17-
public List<IExifValue> Values { get; } = new();
17+
public List<IExifValue> Values { get; } = [];
1818

1919
public ulong NextIfdOffset { get; private set; }
2020

@@ -31,8 +31,6 @@ public void ReadTags(bool isBigTiff, ulong ifdOffset)
3131
{
3232
this.ReadValues64(this.Values, ifdOffset);
3333
this.NextIfdOffset = this.ReadUInt64();
34-
35-
//// this.ReadSubIfd64(this.Values);
3634
}
3735
}
3836

@@ -62,9 +60,9 @@ public void ReadFileHeader()
6260
{
6361
this.IsBigTiff = true;
6462

65-
ushort bytesize = this.ReadUInt16();
63+
ushort byteSize = this.ReadUInt16();
6664
ushort reserve = this.ReadUInt16();
67-
if (bytesize == TiffConstants.BigTiffByteSize && reserve == 0)
65+
if (byteSize == TiffConstants.BigTiffByteSize && reserve == 0)
6866
{
6967
this.FirstIfdOffset = this.ReadUInt64();
7068
return;

src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,22 @@ internal static class TiffDecoderOptionsParser
2525
/// <returns>True, if the image uses tiles. Otherwise the images has strip's.</returns>
2626
public static bool VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata)
2727
{
28-
IExifValue extraSamplesExifValue = exifProfile.GetValueInternal(ExifTag.ExtraSamples);
29-
if (extraSamplesExifValue is not null)
28+
if (exifProfile.TryGetValue(ExifTag.ExtraSamples, out IExifValue<ushort[]> samples))
3029
{
31-
short[] extraSamples = (short[])extraSamplesExifValue.GetValue();
32-
if (extraSamples.Length != 1)
30+
// We only support a single sample pertaining to alpha data.
31+
// Other information is discarded.
32+
TiffExtraSampleType sampleType = (TiffExtraSampleType)samples.Value[0];
33+
if (sampleType is TiffExtraSampleType.CorelDrawUnassociatedAlphaData)
3334
{
34-
TiffThrowHelper.ThrowNotSupported("ExtraSamples is only supported with one extra sample for alpha data.");
35+
// According to libtiff, this CorelDRAW-specific value indicates unassociated alpha.
36+
// Patch required for compatibility with malformed CorelDRAW-generated TIFFs.
37+
// https://libtiff.gitlab.io/libtiff/releases/v3.9.0beta.html
38+
sampleType = TiffExtraSampleType.UnassociatedAlphaData;
3539
}
3640

37-
TiffExtraSampleType extraSamplesType = (TiffExtraSampleType)extraSamples[0];
38-
options.ExtraSamplesType = extraSamplesType;
39-
if (extraSamplesType is not (TiffExtraSampleType.UnassociatedAlphaData or TiffExtraSampleType.AssociatedAlphaData))
41+
if (sampleType is (TiffExtraSampleType.UnassociatedAlphaData or TiffExtraSampleType.AssociatedAlphaData))
4042
{
41-
TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is not supported with UnspecifiedData.");
43+
options.ExtraSamplesType = sampleType;
4244
}
4345
}
4446

src/ImageSharp/Formats/Tiff/TiffExtraSampleType.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,11 @@ internal enum TiffExtraSampleType
2222
/// The extra data is unassociated alpha data is transparency information that logically exists independent of an image;
2323
/// it is commonly called a soft matte.
2424
/// </summary>
25-
UnassociatedAlphaData = 2
25+
UnassociatedAlphaData = 2,
26+
27+
/// <summary>
28+
/// A CorelDRAW-specific value observed in damaged files, indicating unassociated alpha.
29+
/// Not part of the official TIFF specification; patched in ImageSharp for compatibility.
30+
/// </summary>
31+
CorelDrawUnassociatedAlphaData = 999,
2632
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
55

66
/// <summary>
7-
/// Specifies exif data types.
7+
/// Specifies Exif data types.
88
/// </summary>
99
public enum ExifDataType
1010
{

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

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ internal static class ExifEncodedStringHelpers
1616
private const ulong UnicodeCode = 0x_45_44_4F_43_49_4E_55;
1717
private const ulong UndefinedCode = 0x_00_00_00_00_00_00_00_00;
1818

19-
private static ReadOnlySpan<byte> AsciiCodeBytes => new byte[] { 0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0 };
19+
private static ReadOnlySpan<byte> AsciiCodeBytes => [0x41, 0x53, 0x43, 0x49, 0x49, 0, 0, 0];
2020

21-
private static ReadOnlySpan<byte> JISCodeBytes => new byte[] { 0x4A, 0x49, 0x53, 0, 0, 0, 0, 0 };
21+
private static ReadOnlySpan<byte> JISCodeBytes => [0x4A, 0x49, 0x53, 0, 0, 0, 0, 0];
2222

23-
private static ReadOnlySpan<byte> UnicodeCodeBytes => new byte[] { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0 };
23+
private static ReadOnlySpan<byte> UnicodeCodeBytes => [0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0];
2424

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

2727
// 20932 EUC-JP Japanese (JIS 0208-1990 and 0212-1990)
2828
// https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding?view=net-6.0
@@ -50,37 +50,60 @@ 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.StartsWith((ReadOnlySpan<byte>)[0xFF, 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+
78+
if (textBuffer.StartsWith((ReadOnlySpan<byte>)[0xFE, 0xFF]))
79+
{
80+
// Big-endian BOM
81+
string text = Encoding.BigEndianUnicode.GetString(textBuffer[2..]);
82+
encodedString = new EncodedString(code, text);
83+
return true;
84+
}
85+
}
86+
87+
{
88+
string text = GetEncoding(code, order).GetString(textBuffer);
89+
encodedString = new EncodedString(code, text);
90+
return true;
91+
}
6992
}
7093

7194
encodedString = default;
7295
return false;
7396
}
7497

7598
public static uint GetDataLength(EncodedString encodedString) =>
76-
(uint)GetEncoding(encodedString.Code).GetByteCount(encodedString.Text) + CharacterCodeBytesLength;
99+
(uint)GetEncoding(encodedString.Code, ByteOrder.LittleEndian).GetByteCount(encodedString.Text) + CharacterCodeBytesLength;
77100

78101
public static int Write(EncodedString encodedString, Span<byte> destination)
79102
{
80103
GetCodeBytes(encodedString.Code).CopyTo(destination);
81104

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

85108
return CharacterCodeBytesLength + count;
86109
}
@@ -92,8 +115,7 @@ private static bool TryDetect(ReadOnlySpan<byte> buffer, out CharacterCode code)
92115
{
93116
if (buffer.Length >= CharacterCodeBytesLength)
94117
{
95-
ulong test = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
96-
switch (test)
118+
switch (BinaryPrimitives.ReadUInt64LittleEndian(buffer))
97119
{
98120
case AsciiCode:
99121
code = CharacterCode.ASCII;
@@ -108,7 +130,8 @@ private static bool TryDetect(ReadOnlySpan<byte> buffer, out CharacterCode code)
108130
code = CharacterCode.Undefined;
109131
return true;
110132
default:
111-
break;
133+
code = default;
134+
return false;
112135
}
113136
}
114137

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

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public bool TryCreateThumbnail<TPixel>([NotNullWhen(true)] out Image<TPixel>? im
170170
/// <summary>
171171
/// Returns the value with the specified tag.
172172
/// </summary>
173-
/// <param name="tag">The tag of the exif value.</param>
173+
/// <param name="tag">The tag of the Exif value.</param>
174174
/// <param name="exifValue">The value with the specified tag.</param>
175175
/// <returns>True when found, otherwise false</returns>
176176
/// <typeparam name="TValueType">The data type of the tag.</typeparam>
@@ -214,7 +214,7 @@ public bool RemoveValue(ExifTag tag)
214214
/// <summary>
215215
/// Sets the value of the specified tag.
216216
/// </summary>
217-
/// <param name="tag">The tag of the exif value.</param>
217+
/// <param name="tag">The tag of the Exif value.</param>
218218
/// <param name="value">The value.</param>
219219
/// <typeparam name="TValueType">The data type of the tag.</typeparam>
220220
public void SetValue<TValueType>(ExifTag<TValueType> tag, TValueType value)
@@ -233,7 +233,7 @@ public void SetValue<TValueType>(ExifTag<TValueType> tag, TValueType value)
233233

234234
if (this.values.Count == 0)
235235
{
236-
return Array.Empty<byte>();
236+
return [];
237237
}
238238

239239
ExifWriter writer = new(this.values, this.Parts);
@@ -246,7 +246,7 @@ public void SetValue<TValueType>(ExifTag<TValueType> tag, TValueType value)
246246
/// <summary>
247247
/// Returns the value with the specified tag.
248248
/// </summary>
249-
/// <param name="tag">The tag of the exif value.</param>
249+
/// <param name="tag">The tag of the Exif value.</param>
250250
/// <returns>The value with the specified tag.</returns>
251251
internal IExifValue? GetValueInternal(ExifTag tag)
252252
{
@@ -264,7 +264,7 @@ public void SetValue<TValueType>(ExifTag<TValueType> tag, TValueType value)
264264
/// <summary>
265265
/// Sets the value of the specified tag.
266266
/// </summary>
267-
/// <param name="tag">The tag of the exif value.</param>
267+
/// <param name="tag">The tag of the Exif value.</param>
268268
/// <param name="value">The value.</param>
269269
/// <exception cref="NotSupportedException">The newly created value is null.</exception>
270270
internal void SetValueInternal(ExifTag tag, object? value)
@@ -278,11 +278,7 @@ internal void SetValueInternal(ExifTag tag, object? value)
278278
}
279279
}
280280

281-
ExifValue? newExifValue = ExifValues.Create(tag);
282-
if (newExifValue is null)
283-
{
284-
throw new NotSupportedException($"Newly created value for tag {tag} is null.");
285-
}
281+
ExifValue? newExifValue = ExifValues.Create(tag) ?? throw new NotSupportedException($"Newly created value for tag {tag} is null.");
286282

287283
newExifValue.TrySetValue(value);
288284
this.values.Add(newExifValue);
@@ -402,7 +398,7 @@ private void InitializeValues()
402398

403399
if (this.data is null)
404400
{
405-
this.values = new List<IExifValue>();
401+
this.values = [];
406402
return;
407403
}
408404

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

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public ExifReader(byte[] exifData, MemoryAllocator? allocator)
3434
/// </returns>
3535
public List<IExifValue> ReadValues()
3636
{
37-
List<IExifValue> values = new();
37+
List<IExifValue> values = [];
3838

3939
// II == 0x4949
4040
this.IsBigEndian = this.ReadUInt16() != 0x4949;
@@ -64,7 +64,7 @@ private void GetThumbnail(uint offset)
6464
return;
6565
}
6666

67-
List<IExifValue> values = new();
67+
List<IExifValue> values = [];
6868
this.ReadValues(values, offset);
6969

7070
for (int i = 0; i < values.Count; i++)
@@ -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
{
@@ -104,7 +104,7 @@ protected BaseExifReader(Stream stream, MemoryAllocator? allocator)
104104
/// <summary>
105105
/// Gets the invalid tags.
106106
/// </summary>
107-
public IReadOnlyList<ExifTag> InvalidTags => this.invalidTags ?? (IReadOnlyList<ExifTag>)Array.Empty<ExifTag>();
107+
public IReadOnlyList<ExifTag> InvalidTags => this.invalidTags ?? (IReadOnlyList<ExifTag>)[];
108108

109109
/// <summary>
110110
/// Gets or sets the thumbnail length in the byte stream.
@@ -116,9 +116,19 @@ 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

121-
public List<(ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif)> BigValues { get; } = new();
131+
public List<(ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif)> BigValues { get; } = [];
122132

123133
protected void ReadBigValues(List<IExifValue> values)
124134
{
@@ -486,14 +496,21 @@ private void ReadValue64(List<IExifValue> values, Span<byte> offsetBuffer)
486496

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

494511
foreach (IExifValue val in values)
495512
{
496-
// to skip duplicates must be used Equals method,
513+
// To skip duplicates must be used Equals method,
497514
// == operator not defined for ExifValue and IExifValue
498515
if (exif.Equals(val))
499516
{
@@ -517,10 +534,10 @@ private void Add(IList<IExifValue> values, ExifValue exif, object? value)
517534
}
518535

519536
private void AddInvalidTag(ExifTag tag)
520-
=> (this.invalidTags ??= new List<ExifTag>()).Add(tag);
537+
=> (this.invalidTags ??= []).Add(tag);
521538

522539
private void AddSubIfd(object? val)
523-
=> (this.subIfds ??= new List<ulong>()).Add(Convert.ToUInt64(val, CultureInfo.InvariantCulture));
540+
=> (this.subIfds ??= []).Add(Convert.ToUInt64(val, CultureInfo.InvariantCulture));
524541

525542
private void Seek(ulong pos)
526543
=> this.data.Seek((long)pos, SeekOrigin.Begin);

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public byte[] GetData()
5555

5656
if (length == 0)
5757
{
58-
return Array.Empty<byte>();
58+
return [];
5959
}
6060

6161
// two bytes for the byte Order marker 'II' or 'MM', followed by the number 42 (0x2A) and a 0, making 4 bytes total
@@ -197,7 +197,7 @@ private static int WriteInt32(int value, Span<byte> destination, int offset)
197197

198198
private List<IExifValue> GetPartValues(ExifParts part)
199199
{
200-
List<IExifValue> result = new();
200+
List<IExifValue> result = [];
201201

202202
if (!EnumUtils.HasFlag(this.allowedParts, part))
203203
{
@@ -332,7 +332,7 @@ private int WriteData(uint startIndex, List<IExifValue> values, Span<byte> desti
332332

333333
private int WriteHeaders(List<IExifValue> values, Span<byte> destination, int offset)
334334
{
335-
this.dataOffsets = new List<int>();
335+
this.dataOffsets = [];
336336

337337
int newOffset = WriteUInt16((ushort)values.Count, destination, offset);
338338

0 commit comments

Comments
 (0)