Skip to content

Commit 1f990bf

Browse files
authored
Merge pull request #393 from iamcarbon/cq4
Introduce BufferReader and eliminate a bunch of allocations
2 parents 55808d1 + 1b38f68 commit 1f990bf

File tree

12 files changed

+293
-187
lines changed

12 files changed

+293
-187
lines changed

MetadataExtractor/Formats/Apple/BplistReader.cs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,27 +45,28 @@ public static PropertyListResults Parse(byte[] bplist)
4545

4646
Trailer trailer = ReadTrailer();
4747

48-
SequentialByteArrayReader reader = new(bplist, baseIndex: checked((int)(trailer.OffsetTableOffset + trailer.TopObject)));
48+
int offset = checked((int)(trailer.OffsetTableOffset + trailer.TopObject));
49+
var reader = new BufferReader(bplist.AsSpan(offset), isBigEndian: true);
4950

5051
int[] offsets = new int[(int)trailer.NumObjects];
5152

52-
for (long i = 0; i < trailer.NumObjects; i++)
53+
for (int i = 0; i < (int)trailer.NumObjects; i++)
5354
{
5455
if (trailer.OffsetIntSize == 1)
5556
{
56-
offsets[(int)i] = reader.GetByte();
57+
offsets[i] = reader.GetByte();
5758
}
5859
else if (trailer.OffsetIntSize == 2)
5960
{
60-
offsets[(int)i] = reader.GetUInt16();
61+
offsets[i] = reader.GetUInt16();
6162
}
6263
}
6364

6465
List<object> objects = [];
6566

6667
for (int i = 0; i < offsets.Length; i++)
6768
{
68-
reader = new SequentialByteArrayReader(bplist, offsets[i]);
69+
reader = new BufferReader(bplist.AsSpan(offsets[i]), isBigEndian: true);
6970

7071
byte b = reader.GetByte();
7172

@@ -75,13 +76,13 @@ public static PropertyListResults Parse(byte[] bplist)
7576
object obj = objectFormat switch
7677
{
7778
// dict
78-
0x0D => HandleDict(marker),
79+
0x0D => HandleDict(ref reader, marker),
7980
// string (ASCII)
8081
0x05 => reader.GetString(bytesRequested: marker & 0x0F, Encoding.ASCII),
8182
// data
82-
0x04 => HandleData(marker),
83+
0x04 => HandleData(ref reader, marker),
8384
// int
84-
0x01 => HandleInt(marker),
85+
0x01 => HandleInt(ref reader, marker),
8586
// unknown
8687
_ => throw new NotSupportedException($"Unsupported object format {objectFormat:X2}.")
8788
};
@@ -93,10 +94,10 @@ public static PropertyListResults Parse(byte[] bplist)
9394

9495
Trailer ReadTrailer()
9596
{
96-
SequentialByteArrayReader reader = new(bplist, bplist.Length - Trailer.SizeBytes);
97+
var reader = new BufferReader(bplist.AsSpan(bplist.Length - Trailer.SizeBytes), isBigEndian: true);
9798

9899
// Skip 5-byte unused values, 1-byte sort version.
99-
reader.Skip(6);
100+
reader.Skip(5 + 1);
100101

101102
return new Trailer
102103
{
@@ -108,7 +109,7 @@ Trailer ReadTrailer()
108109
};
109110
}
110111

111-
object HandleInt(byte marker)
112+
static object HandleInt(ref BufferReader reader, byte marker)
112113
{
113114
return marker switch
114115
{
@@ -120,7 +121,7 @@ object HandleInt(byte marker)
120121
};
121122
}
122123

123-
Dictionary<byte, byte> HandleDict(byte count)
124+
static Dictionary<byte, byte> HandleDict(ref BufferReader reader, byte count)
124125
{
125126
var keyRefs = ArrayPool<byte>.Shared.Rent(count);
126127

@@ -141,7 +142,7 @@ Dictionary<byte, byte> HandleDict(byte count)
141142
return map;
142143
}
143144

144-
object HandleData(byte marker)
145+
object HandleData(ref BufferReader reader, byte marker)
145146
{
146147
int byteCount = marker;
147148

MetadataExtractor/Formats/Exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.cs

Lines changed: 42 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -160,30 +160,16 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin
160160
return null;
161161

162162
var sb = new StringBuilder();
163-
switch (values[0])
163+
sb.Append(values[0] switch
164164
{
165-
case 0:
166-
sb.Append("Single AF");
167-
break;
168-
case 1:
169-
sb.Append("Sequential shooting AF");
170-
break;
171-
case 2:
172-
sb.Append("Continuous AF");
173-
break;
174-
case 3:
175-
sb.Append("Multi AF");
176-
break;
177-
case 4:
178-
sb.Append("Face detect");
179-
break;
180-
case 10:
181-
sb.Append("MF");
182-
break;
183-
default:
184-
sb.Append("Unknown (" + values[0] + ")");
185-
break;
186-
}
165+
0 => "Single AF",
166+
1 => "Sequential shooting AF",
167+
2 => "Continuous AF",
168+
3 => "Multi AF",
169+
4 => "Face detect",
170+
10 => "MF",
171+
_ => $"Unknown ({values[0]})"
172+
});
187173

188174
if (values.Length > 1)
189175
{
@@ -231,18 +217,12 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin
231217

232218
var sb = new StringBuilder();
233219

234-
switch (values[0])
220+
sb.Append(values[0] switch
235221
{
236-
case 0:
237-
sb.Append("AF not used");
238-
break;
239-
case 1:
240-
sb.Append("AF used");
241-
break;
242-
default:
243-
sb.Append("Unknown (" + values[0] + ")");
244-
break;
245-
}
222+
0 => "AF not used",
223+
1 => "AF used",
224+
_ => $"Unknown ({values[0]})"
225+
});
246226

247227
if (values.Length > 1)
248228
sb.Append("; " + values[1]);
@@ -385,24 +365,14 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin
385365

386366
var sb = new StringBuilder();
387367

388-
switch (values[0])
368+
sb.Append(values[0] switch
389369
{
390-
case 0:
391-
sb.Append("Off");
392-
break;
393-
case 3:
394-
sb.Append("TTL");
395-
break;
396-
case 4:
397-
sb.Append("Auto");
398-
break;
399-
case 5:
400-
sb.Append("Manual");
401-
break;
402-
default:
403-
sb.Append("Unknown (" + values[0] + ")");
404-
break;
405-
}
370+
0 => "Off",
371+
3 => "TTL",
372+
4 => "Auto",
373+
5 => "Manual",
374+
_ => $"Unknown ({values[0]})"
375+
});
406376

407377
for (var i = 1; i < values.Length; i++)
408378
sb.Append("; ").Append(values[i]);
@@ -692,33 +662,17 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin
692662
return null;
693663

694664
var sb = new StringBuilder();
695-
switch (values[0])
665+
sb.Append(values[0] switch
696666
{
697-
case 1:
698-
sb.Append("Vivid");
699-
break;
700-
case 2:
701-
sb.Append("Natural");
702-
break;
703-
case 3:
704-
sb.Append("Muted");
705-
break;
706-
case 4:
707-
sb.Append("Portrait");
708-
break;
709-
case 5:
710-
sb.Append("i-Enhance");
711-
break;
712-
case 256:
713-
sb.Append("Monotone");
714-
break;
715-
case 512:
716-
sb.Append("Sepia");
717-
break;
718-
default:
719-
sb.Append("Unknown (").Append(values[0]).Append(')');
720-
break;
721-
}
667+
1 => "Vivid",
668+
2 => "Natural",
669+
3 => "Muted",
670+
4 => "Portrait",
671+
5 => "i-Enhance",
672+
256 => "Monotone",
673+
512 => "Sepia",
674+
_ => $"Unknown ({values[0]})"
675+
});
722676

723677
if (values.Length > 1)
724678
sb.Append("; ").Append(values[1]);
@@ -822,33 +776,18 @@ public sealed class OlympusCameraSettingsMakernoteDescriptor(OlympusCameraSettin
822776
sb.Append("Partial Color " + values[i] + "; ");
823777
else if (i == 4)
824778
{
825-
switch (values[i])
779+
sb.Append(values[i] switch
826780
{
827-
case 0x0000:
828-
sb.Append("No Effect");
829-
break;
830-
case 0x8010:
831-
sb.Append("Star Light");
832-
break;
833-
case 0x8020:
834-
sb.Append("Pin Hole");
835-
break;
836-
case 0x8030:
837-
sb.Append("Frame");
838-
break;
839-
case 0x8040:
840-
sb.Append("Soft Focus");
841-
break;
842-
case 0x8050:
843-
sb.Append("White Edge");
844-
break;
845-
case 0x8060:
846-
sb.Append("B&W");
847-
break;
848-
default:
849-
sb.Append("Unknown (").Append(values[i]).Append(')');
850-
break;
851-
}
781+
0x0000 => "No Effect",
782+
0x8010 => "Star Light",
783+
0x8020 => "Pin Hole",
784+
0x8030 => "Frame",
785+
0x8040 => "Soft Focus",
786+
0x8050 => "White Edge",
787+
0x8060 => "B&W",
788+
_ => $"Unknown ({values[i]})"
789+
});
790+
852791
sb.Append("; ");
853792
}
854793
else if (i == 6)

MetadataExtractor/Formats/Exif/makernotes/OlympusMakernoteDirectory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,9 @@ public override void Set(int tagType, object value)
448448
base.Set(tagType, value);
449449
}
450450

451-
private void ProcessCameraSettings(byte[] bytes)
451+
private void ProcessCameraSettings(ReadOnlySpan<byte> bytes)
452452
{
453-
var reader = new SequentialByteArrayReader(bytes);
453+
var reader = new BufferReader(bytes, isBigEndian: true);
454454
var count = bytes.Length / 4;
455455

456456
for (var i = 0; i < count; i++)

MetadataExtractor/Formats/Flir/FlirReader.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,9 @@ public IEnumerable<Directory> Extract(IndexedReader reader)
163163
directory.Set(TagRawValueMedian, reader2.GetUInt16(TagRawValueMedian));
164164
directory.Set(TagRawValueRange, reader2.GetUInt16(TagRawValueRange));
165165

166-
var dateTimeBytes = reader2.GetBytes(TagDateTimeOriginal, 10);
167-
var dateTimeReader = new SequentialByteArrayReader(dateTimeBytes, isMotorolaByteOrder: false);
166+
Span<byte> dateTimeBytes = stackalloc byte[10];
167+
reader2.GetBytes(TagDateTimeOriginal, dateTimeBytes);
168+
var dateTimeReader = new BufferReader(dateTimeBytes, isBigEndian: false);
168169
var tm = dateTimeReader.GetUInt32();
169170
var ss = dateTimeReader.GetUInt32() & 0xffff;
170171
var tz = dateTimeReader.GetInt16();

MetadataExtractor/Formats/Jpeg/JpegReader.cs

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,45 @@ public JpegDirectory Extract(JpegSegment segment)
2929
// The value of TagCompressionType is determined by the segment type found
3030
directory.Set(JpegDirectory.TagCompressionType, (int)segment.Type - (int)JpegSegmentType.Sof0);
3131

32-
SequentialReader reader = new SequentialByteArrayReader(segment.Bytes);
32+
const int JpegHeaderSize = 1 + 2 + 2 + 1;
3333

34-
try
34+
if (segment.Span.Length < JpegHeaderSize)
3535
{
36-
directory.Set(JpegDirectory.TagDataPrecision, reader.GetByte());
37-
directory.Set(JpegDirectory.TagImageHeight, reader.GetUInt16());
38-
directory.Set(JpegDirectory.TagImageWidth, reader.GetUInt16());
39-
40-
var componentCount = reader.GetByte();
41-
42-
directory.Set(JpegDirectory.TagNumberOfComponents, componentCount);
43-
44-
// For each component, there are three bytes of data:
45-
// 1 - Component ID: 1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q
46-
// 2 - Sampling factors: bit 0-3 vertical, 4-7 horizontal
47-
// 3 - Quantization table number
48-
49-
for (var i = 0; i < componentCount; i++)
50-
{
51-
var componentId = reader.GetByte();
52-
var samplingFactorByte = reader.GetByte();
53-
var quantizationTableNumber = reader.GetByte();
54-
var component = new JpegComponent(componentId, samplingFactorByte, quantizationTableNumber);
55-
directory.Set(JpegDirectory.TagComponentData1 + i, component);
56-
}
36+
directory.AddError("Insufficient bytes for JPEG segment header.");
37+
38+
return directory;
5739
}
58-
catch (IOException ex)
40+
41+
var reader = new BufferReader(segment.Span, isBigEndian: true);
42+
43+
directory.Set(JpegDirectory.TagDataPrecision, reader.GetByte());
44+
directory.Set(JpegDirectory.TagImageHeight, reader.GetUInt16());
45+
directory.Set(JpegDirectory.TagImageWidth, reader.GetUInt16());
46+
47+
var componentCount = reader.GetByte();
48+
49+
directory.Set(JpegDirectory.TagNumberOfComponents, componentCount);
50+
51+
const int JpegComponentSize = 1 + 1 + 1;
52+
53+
if (reader.Available < componentCount * JpegComponentSize)
54+
{
55+
directory.AddError("Insufficient bytes for JPEG the requested number of JPEG components.");
56+
return directory;
57+
}
58+
59+
// For each component, there are three bytes of data:
60+
// 1 - Component ID: 1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q
61+
// 2 - Sampling factors: bit 0-3 vertical, 4-7 horizontal
62+
// 3 - Quantization table number
63+
64+
for (var i = 0; i < componentCount; i++)
5965
{
60-
directory.AddError(ex.Message);
66+
var componentId = reader.GetByte();
67+
var samplingFactorByte = reader.GetByte();
68+
var quantizationTableNumber = reader.GetByte();
69+
var component = new JpegComponent(componentId, samplingFactorByte, quantizationTableNumber);
70+
directory.Set(JpegDirectory.TagComponentData1 + i, component);
6171
}
6272

6373
return directory;

MetadataExtractor/Formats/Photoshop/PhotoshopReader.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,15 @@ public IReadOnlyList<Directory> Extract(SequentialReader reader, int length)
5353
// http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037504
5454
var pos = 0;
5555
int clippingPathCount = 0;
56+
57+
Span<byte> signature = stackalloc byte[4];
58+
5659
while (pos < length)
5760
{
5861
try
5962
{
6063
// 4 bytes for the signature ("8BIM", "PHUT", etc.)
61-
var signature = reader.GetString(4, Encoding.UTF8);
64+
reader.GetBytes(signature);
6265
pos += 4;
6366

6467
// 2 bytes for the resource identifier (tag type).
@@ -106,7 +109,7 @@ public IReadOnlyList<Directory> Extract(SequentialReader reader, int length)
106109
}
107110

108111
// Skip any unsupported IRBs
109-
if (signature != "8BIM")
112+
if (!signature.SequenceEqual("8BIM"u8))
110113
continue;
111114

112115
switch (tagType)

0 commit comments

Comments
 (0)