Skip to content

Commit 3d40c91

Browse files
Merge pull request #2415 from gfoidl/allocations
Reduced intermediate allocations
2 parents 2ba22e4 + 53fa6da commit 3d40c91

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+702
-665
lines changed

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ private void ReadRle24<TPixel>(BufferedReadStream stream, Buffer2D<TPixel> pixel
453453
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
454454
private void UncompressRle4(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
455455
{
456+
Span<byte> scratchBuffer = stackalloc byte[128];
456457
Span<byte> cmd = stackalloc byte[2];
457458
int count = 0;
458459

@@ -491,9 +492,9 @@ private void UncompressRle4(BufferedReadStream stream, int w, Span<byte> buffer,
491492
int max = cmd[1];
492493
int bytesToRead = (int)(((uint)max + 1) / 2);
493494

494-
byte[] run = new byte[bytesToRead];
495+
Span<byte> run = bytesToRead <= 128 ? scratchBuffer.Slice(0, bytesToRead) : new byte[bytesToRead];
495496

496-
stream.Read(run, 0, run.Length);
497+
stream.Read(run);
497498

498499
int idx = 0;
499500
for (int i = 0; i < max; i++)
@@ -559,6 +560,7 @@ private void UncompressRle4(BufferedReadStream stream, int w, Span<byte> buffer,
559560
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
560561
private void UncompressRle8(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
561562
{
563+
Span<byte> scratchBuffer = stackalloc byte[128];
562564
Span<byte> cmd = stackalloc byte[2];
563565
int count = 0;
564566

@@ -596,13 +598,13 @@ private void UncompressRle8(BufferedReadStream stream, int w, Span<byte> buffer,
596598
// Take this number of bytes from the stream as uncompressed data.
597599
int length = cmd[1];
598600

599-
byte[] run = new byte[length];
601+
Span<byte> run = length <= 128 ? scratchBuffer.Slice(0, length) : new byte[length];
600602

601-
stream.Read(run, 0, run.Length);
603+
stream.Read(run);
602604

603-
run.AsSpan().CopyTo(buffer[count..]);
605+
run.CopyTo(buffer[count..]);
604606

605-
count += run.Length;
607+
count += length;
606608

607609
// Absolute mode data is aligned to two-byte word-boundary.
608610
int padding = length & 1;
@@ -639,6 +641,7 @@ private void UncompressRle8(BufferedReadStream stream, int w, Span<byte> buffer,
639641
/// <param name="rowsWithUndefinedPixels">Keeps track of rows, which have undefined pixels.</param>
640642
private void UncompressRle24(BufferedReadStream stream, int w, Span<byte> buffer, Span<bool> undefinedPixels, Span<bool> rowsWithUndefinedPixels)
641643
{
644+
Span<byte> scratchBuffer = stackalloc byte[128];
642645
Span<byte> cmd = stackalloc byte[2];
643646
int uncompressedPixels = 0;
644647

@@ -675,17 +678,18 @@ private void UncompressRle24(BufferedReadStream stream, int w, Span<byte> buffer
675678
// If the second byte > 2, we are in 'absolute mode'.
676679
// Take this number of bytes from the stream as uncompressed data.
677680
int length = cmd[1];
681+
int length3 = length * 3;
678682

679-
byte[] run = new byte[length * 3];
683+
Span<byte> run = length3 <= 128 ? scratchBuffer.Slice(0, length3) : new byte[length3];
680684

681-
stream.Read(run, 0, run.Length);
685+
stream.Read(run);
682686

683-
run.AsSpan().CopyTo(buffer[(uncompressedPixels * 3)..]);
687+
run.CopyTo(buffer[(uncompressedPixels * 3)..]);
684688

685689
uncompressedPixels += length;
686690

687691
// Absolute mode data is aligned to two-byte word-boundary.
688-
int padding = run.Length & 1;
692+
int padding = length3 & 1;
689693

690694
stream.Skip(padding);
691695

@@ -1286,18 +1290,18 @@ private void ReadInfoHeader(BufferedReadStream stream)
12861290
// color masks for each color channel follow the info header.
12871291
if (this.infoHeader.Compression == BmpCompression.BitFields)
12881292
{
1289-
byte[] bitfieldsBuffer = new byte[12];
1290-
stream.Read(bitfieldsBuffer, 0, 12);
1291-
Span<byte> data = bitfieldsBuffer.AsSpan();
1293+
Span<byte> bitfieldsBuffer = stackalloc byte[12];
1294+
stream.Read(bitfieldsBuffer);
1295+
Span<byte> data = bitfieldsBuffer;
12921296
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]);
12931297
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));
12941298
this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4));
12951299
}
12961300
else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS)
12971301
{
1298-
byte[] bitfieldsBuffer = new byte[16];
1299-
stream.Read(bitfieldsBuffer, 0, 16);
1300-
Span<byte> data = bitfieldsBuffer.AsSpan();
1302+
Span<byte> bitfieldsBuffer = stackalloc byte[16];
1303+
stream.Read(bitfieldsBuffer);
1304+
Span<byte> data = bitfieldsBuffer;
13011305
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]);
13021306
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));
13031307
this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4));
@@ -1470,7 +1474,7 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
14701474
{
14711475
// Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit.
14721476
// Make sure, that we will not read pass the bitmap offset (starting position of image data).
1473-
if ((stream.Position + colorMapSizeBytes) > this.fileHeader.Offset)
1477+
if (stream.Position > this.fileHeader.Offset - colorMapSizeBytes)
14741478
{
14751479
BmpThrowHelper.ThrowInvalidImageContentException(
14761480
$"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset.");

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
2222
/// <summary>
2323
/// The temp buffer used to reduce allocations.
2424
/// </summary>
25-
private readonly byte[] buffer = new byte[16];
25+
private ScratchBuffer buffer; // mutable struct, don't make readonly
2626

2727
/// <summary>
2828
/// The global color table.
@@ -249,13 +249,13 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
249249
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
250250
private void ReadGraphicalControlExtension(BufferedReadStream stream)
251251
{
252-
int bytesRead = stream.Read(this.buffer, 0, 6);
252+
int bytesRead = stream.Read(this.buffer.Span, 0, 6);
253253
if (bytesRead != 6)
254254
{
255255
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension");
256256
}
257257

258-
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer);
258+
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer.Span);
259259
}
260260

261261
/// <summary>
@@ -264,13 +264,13 @@ private void ReadGraphicalControlExtension(BufferedReadStream stream)
264264
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
265265
private void ReadImageDescriptor(BufferedReadStream stream)
266266
{
267-
int bytesRead = stream.Read(this.buffer, 0, 9);
267+
int bytesRead = stream.Read(this.buffer.Span, 0, 9);
268268
if (bytesRead != 9)
269269
{
270270
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor");
271271
}
272272

273-
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer);
273+
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer.Span);
274274
if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0)
275275
{
276276
GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0");
@@ -283,13 +283,13 @@ private void ReadImageDescriptor(BufferedReadStream stream)
283283
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
284284
private void ReadLogicalScreenDescriptor(BufferedReadStream stream)
285285
{
286-
int bytesRead = stream.Read(this.buffer, 0, 7);
286+
int bytesRead = stream.Read(this.buffer.Span, 0, 7);
287287
if (bytesRead != 7)
288288
{
289289
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor");
290290
}
291291

292-
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
292+
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer.Span);
293293
}
294294

295295
/// <summary>
@@ -306,8 +306,8 @@ private void ReadApplicationExtension(BufferedReadStream stream)
306306
long position = stream.Position;
307307
if (appLength == GifConstants.ApplicationBlockSize)
308308
{
309-
stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
310-
bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes);
309+
stream.Read(this.buffer.Span, 0, GifConstants.ApplicationBlockSize);
310+
bool isXmp = this.buffer.Span.StartsWith(GifConstants.XmpApplicationIdentificationBytes);
311311
if (isXmp && !this.skipMetadata)
312312
{
313313
GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator);
@@ -331,8 +331,8 @@ private void ReadApplicationExtension(BufferedReadStream stream)
331331
// http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
332332
if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize)
333333
{
334-
stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize);
335-
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount;
334+
stream.Read(this.buffer.Span, 0, GifConstants.NetscapeLoopingSubBlockSize);
335+
this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span.Slice(1)).RepeatCount;
336336
stream.Skip(1); // Skip the terminator.
337337
return;
338338
}
@@ -762,4 +762,12 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(BufferedReadStream s
762762
}
763763
}
764764
}
765+
766+
private unsafe struct ScratchBuffer
767+
{
768+
private const int Size = 16;
769+
private fixed byte scratch[Size];
770+
771+
public Span<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
772+
}
765773
}

src/ImageSharp/Formats/Gif/GifEncoderCore.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
2828
/// </summary>
2929
private readonly Configuration configuration;
3030

31-
/// <summary>
32-
/// A reusable buffer used to reduce allocations.
33-
/// </summary>
34-
private readonly byte[] buffer = new byte[20];
35-
3631
/// <summary>
3732
/// Whether to skip metadata during encode.
3833
/// </summary>
@@ -324,9 +319,10 @@ private void WriteLogicalScreenDescriptor(
324319
backgroundColorIndex: unchecked((byte)transparencyIndex),
325320
ratio);
326321

327-
descriptor.WriteTo(this.buffer);
322+
Span<byte> buffer = stackalloc byte[20];
323+
descriptor.WriteTo(buffer);
328324

329-
stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size);
325+
stream.Write(buffer, 0, GifLogicalScreenDescriptor.Size);
330326
}
331327

332328
/// <summary>
@@ -365,12 +361,14 @@ private void WriteComments(GifMetadata metadata, Stream stream)
365361
return;
366362
}
367363

364+
Span<byte> buffer = stackalloc byte[2];
365+
368366
for (int i = 0; i < metadata.Comments.Count; i++)
369367
{
370368
string comment = metadata.Comments[i];
371-
this.buffer[0] = GifConstants.ExtensionIntroducer;
372-
this.buffer[1] = GifConstants.CommentLabel;
373-
stream.Write(this.buffer, 0, 2);
369+
buffer[1] = GifConstants.CommentLabel;
370+
buffer[0] = GifConstants.ExtensionIntroducer;
371+
stream.Write(buffer);
374372

375373
// Comment will be stored in chunks of 255 bytes, if it exceeds this size.
376374
ReadOnlySpan<char> commentSpan = comment.AsSpan();
@@ -437,22 +435,23 @@ private void WriteGraphicalControlExtension(GifFrameMetadata metadata, int trans
437435
private void WriteExtension<TGifExtension>(TGifExtension extension, Stream stream)
438436
where TGifExtension : struct, IGifExtension
439437
{
440-
IMemoryOwner<byte>? owner = null;
441-
Span<byte> extensionBuffer;
442438
int extensionSize = extension.ContentLength;
443439

444440
if (extensionSize == 0)
445441
{
446442
return;
447443
}
448-
else if (extensionSize > this.buffer.Length - 3)
444+
445+
IMemoryOwner<byte>? owner = null;
446+
Span<byte> extensionBuffer = stackalloc byte[0]; // workaround compiler limitation
447+
if (extensionSize > 128)
449448
{
450449
owner = this.memoryAllocator.Allocate<byte>(extensionSize + 3);
451450
extensionBuffer = owner.GetSpan();
452451
}
453452
else
454453
{
455-
extensionBuffer = this.buffer;
454+
extensionBuffer = stackalloc byte[extensionSize + 3];
456455
}
457456

458457
extensionBuffer[0] = GifConstants.ExtensionIntroducer;
@@ -489,9 +488,10 @@ private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, bool hasColo
489488
height: (ushort)image.Height,
490489
packed: packedValue);
491490

492-
descriptor.WriteTo(this.buffer);
491+
Span<byte> buffer = stackalloc byte[20];
492+
descriptor.WriteTo(buffer);
493493

494-
stream.Write(this.buffer, 0, GifImageDescriptor.Size);
494+
stream.Write(buffer, 0, GifImageDescriptor.Size);
495495
}
496496

497497
/// <summary>

src/ImageSharp/Formats/Jpeg/Components/Decoder/AdobeMarker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ private AdobeMarker(short dctEncodeVersion, short app14Flags0, short app14Flags1
6262
/// </summary>
6363
/// <param name="bytes">The byte array containing metadata to parse.</param>
6464
/// <param name="marker">The marker to return.</param>
65-
public static bool TryParse(byte[] bytes, out AdobeMarker marker)
65+
public static bool TryParse(ReadOnlySpan<byte> bytes, out AdobeMarker marker)
6666
{
6767
if (ProfileResolver.IsProfile(bytes, ProfileResolver.AdobeMarker))
6868
{

src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
5353

5454
private ArithmeticDecodingTable[] acDecodingTables;
5555

56+
// Don't make this a ReadOnlySpan<byte>, as the values need to get updated.
5657
private readonly byte[] fixedBin = { 113, 0, 0, 0 };
5758

5859
private readonly CancellationToken cancellationToken;

src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private JFifMarker(byte majorVersion, byte minorVersion, byte densityUnits, shor
6969
/// </summary>
7070
/// <param name="bytes">The byte array containing metadata to parse.</param>
7171
/// <param name="marker">The marker to return.</param>
72-
public static bool TryParse(byte[] bytes, out JFifMarker marker)
72+
public static bool TryParse(ReadOnlySpan<byte> bytes, out JFifMarker marker)
7373
{
7474
if (ProfileResolver.IsProfile(bytes, ProfileResolver.JFifMarker))
7575
{

0 commit comments

Comments
 (0)