Skip to content

Commit 31424c0

Browse files
committed
Reduced intermediate allocations: Png
1 parent d9e8d79 commit 31424c0

File tree

2 files changed

+59
-48
lines changed

2 files changed

+59
-48
lines changed

src/ImageSharp/Formats/Png/PngDecoderCore.cs

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ namespace SixLabors.ImageSharp.Formats.Png;
2828
/// </summary>
2929
internal sealed class PngDecoderCore : IImageDecoderInternals
3030
{
31-
/// <summary>
32-
/// Reusable buffer.
33-
/// </summary>
34-
private readonly byte[] buffer = new byte[4];
35-
3631
/// <summary>
3732
/// The general decoder options.
3833
/// </summary>
@@ -154,9 +149,11 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
154149
this.currentStream = stream;
155150
this.currentStream.Skip(8);
156151
Image<TPixel> image = null;
152+
Span<byte> buffer = stackalloc byte[20];
153+
157154
try
158155
{
159-
while (this.TryReadChunk(out PngChunk chunk))
156+
while (this.TryReadChunk(buffer, out PngChunk chunk))
160157
{
161158
try
162159
{
@@ -252,10 +249,13 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat
252249
ImageMetadata metadata = new();
253250
PngMetadata pngMetadata = metadata.GetPngMetadata();
254251
this.currentStream = stream;
252+
Span<byte> buffer = stackalloc byte[20];
253+
255254
this.currentStream.Skip(8);
255+
256256
try
257257
{
258-
while (this.TryReadChunk(out PngChunk chunk))
258+
while (this.TryReadChunk(buffer, out PngChunk chunk))
259259
{
260260
try
261261
{
@@ -1401,9 +1401,11 @@ private int ReadNextDataChunk()
14011401
return 0;
14021402
}
14031403

1404-
this.currentStream.Read(this.buffer, 0, 4);
1404+
Span<byte> buffer = stackalloc byte[20];
1405+
1406+
this.currentStream.Read(buffer, 0, 4);
14051407

1406-
if (this.TryReadChunk(out PngChunk chunk))
1408+
if (this.TryReadChunk(buffer, out PngChunk chunk))
14071409
{
14081410
if (chunk.Type == PngChunkType.Data)
14091411
{
@@ -1420,11 +1422,12 @@ private int ReadNextDataChunk()
14201422
/// <summary>
14211423
/// Reads a chunk from the stream.
14221424
/// </summary>
1425+
/// <param name="buffer">Temporary buffer.</param>
14231426
/// <param name="chunk">The image format chunk.</param>
14241427
/// <returns>
14251428
/// The <see cref="PngChunk"/>.
14261429
/// </returns>
1427-
private bool TryReadChunk(out PngChunk chunk)
1430+
private bool TryReadChunk(Span<byte> buffer, out PngChunk chunk)
14281431
{
14291432
if (this.nextChunk != null)
14301433
{
@@ -1435,7 +1438,7 @@ private bool TryReadChunk(out PngChunk chunk)
14351438
return true;
14361439
}
14371440

1438-
if (!this.TryReadChunkLength(out int length))
1441+
if (!this.TryReadChunkLength(buffer, out int length))
14391442
{
14401443
chunk = default;
14411444

@@ -1446,15 +1449,15 @@ private bool TryReadChunk(out PngChunk chunk)
14461449
while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position))
14471450
{
14481451
// Not a valid chunk so try again until we reach a known chunk.
1449-
if (!this.TryReadChunkLength(out length))
1452+
if (!this.TryReadChunkLength(buffer, out length))
14501453
{
14511454
chunk = default;
14521455

14531456
return false;
14541457
}
14551458
}
14561459

1457-
PngChunkType type = this.ReadChunkType();
1460+
PngChunkType type = this.ReadChunkType(buffer);
14581461

14591462
// If we're reading color metadata only we're only interested in the IHDR and tRNS chunks.
14601463
// We can skip all other chunk data in the stream for better performance.
@@ -1471,7 +1474,7 @@ private bool TryReadChunk(out PngChunk chunk)
14711474
type: type,
14721475
data: this.ReadChunkData(length));
14731476

1474-
this.ValidateChunk(chunk);
1477+
this.ValidateChunk(chunk, buffer);
14751478

14761479
// Restore the stream position for IDAT chunks, because it will be decoded later and
14771480
// was only read to verifying the CRC is correct.
@@ -1487,9 +1490,10 @@ private bool TryReadChunk(out PngChunk chunk)
14871490
/// Validates the png chunk.
14881491
/// </summary>
14891492
/// <param name="chunk">The <see cref="PngChunk"/>.</param>
1490-
private void ValidateChunk(in PngChunk chunk)
1493+
/// <param name="buffer">Temporary buffer.</param>
1494+
private void ValidateChunk(in PngChunk chunk, Span<byte> buffer)
14911495
{
1492-
uint inputCrc = this.ReadChunkCrc();
1496+
uint inputCrc = this.ReadChunkCrc(buffer);
14931497

14941498
if (chunk.IsCritical)
14951499
{
@@ -1513,13 +1517,14 @@ private void ValidateChunk(in PngChunk chunk)
15131517
/// <summary>
15141518
/// Reads the cycle redundancy chunk from the data.
15151519
/// </summary>
1520+
/// <param name="buffer">Temporary buffer.</param>
15161521
[MethodImpl(InliningOptions.ShortMethod)]
1517-
private uint ReadChunkCrc()
1522+
private uint ReadChunkCrc(Span<byte> buffer)
15181523
{
15191524
uint crc = 0;
1520-
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
1525+
if (this.currentStream.Read(buffer, 0, 4) == 4)
15211526
{
1522-
crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
1527+
crc = BinaryPrimitives.ReadUInt32BigEndian(buffer);
15231528
}
15241529

15251530
return crc;
@@ -1554,15 +1559,16 @@ private IMemoryOwner<byte> ReadChunkData(int length)
15541559
/// <summary>
15551560
/// Identifies the chunk type from the chunk.
15561561
/// </summary>
1562+
/// <param name="buffer">Temporary buffer.</param>
15571563
/// <exception cref="ImageFormatException">
15581564
/// Thrown if the input stream is not valid.
15591565
/// </exception>
15601566
[MethodImpl(InliningOptions.ShortMethod)]
1561-
private PngChunkType ReadChunkType()
1567+
private PngChunkType ReadChunkType(Span<byte> buffer)
15621568
{
1563-
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
1569+
if (this.currentStream.Read(buffer, 0, 4) == 4)
15641570
{
1565-
return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
1571+
return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(buffer);
15661572
}
15671573

15681574
PngThrowHelper.ThrowInvalidChunkType();
@@ -1574,16 +1580,17 @@ private PngChunkType ReadChunkType()
15741580
/// <summary>
15751581
/// Attempts to read the length of the next chunk.
15761582
/// </summary>
1583+
/// <param name="buffer">Temporary buffer.</param>
15771584
/// <param name="result">The result length. If the return type is <see langword="false"/> this parameter is passed uninitialized.</param>
15781585
/// <returns>
15791586
/// Whether the length was read.
15801587
/// </returns>
15811588
[MethodImpl(InliningOptions.ShortMethod)]
1582-
private bool TryReadChunkLength(out int result)
1589+
private bool TryReadChunkLength(Span<byte> buffer, out int result)
15831590
{
1584-
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
1591+
if (this.currentStream.Read(buffer, 0, 4) == 4)
15851592
{
1586-
result = BinaryPrimitives.ReadInt32BigEndian(this.buffer);
1593+
result = BinaryPrimitives.ReadInt32BigEndian(buffer);
15871594

15881595
return true;
15891596
}

src/ImageSharp/Formats/Png/PngEncoderCore.cs

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,10 @@ internal sealed class PngEncoderCore : IImageEncoderInternals, IDisposable
3838
/// </summary>
3939
private readonly Configuration configuration;
4040

41-
/// <summary>
42-
/// Reusable buffer for writing general data.
43-
/// </summary>
44-
private readonly byte[] buffer = new byte[8];
45-
4641
/// <summary>
4742
/// Reusable buffer for writing chunk data.
4843
/// </summary>
49-
private readonly byte[] chunkDataBuffer = new byte[16];
44+
private ScratchBuffer chunkDataBuffer; // mutable struct, don't make readonly
5045

5146
/// <summary>
5247
/// The encoder with options
@@ -576,9 +571,9 @@ private void WriteHeaderChunk(Stream stream)
576571
filterMethod: 0,
577572
interlaceMethod: this.interlaceMode);
578573

579-
header.WriteTo(this.chunkDataBuffer);
574+
header.WriteTo(this.chunkDataBuffer.Span);
580575

581-
this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, PngHeader.Size);
576+
this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer.Span, 0, PngHeader.Size);
582577
}
583578

584579
/// <summary>
@@ -652,9 +647,9 @@ private void WritePhysicalChunk(Stream stream, ImageMetadata meta)
652647
return;
653648
}
654649

655-
PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer);
650+
PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer.Span);
656651

657-
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, PhysicalChunkData.Size);
652+
this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer.Span, 0, PhysicalChunkData.Size);
658653
}
659654

660655
/// <summary>
@@ -880,9 +875,9 @@ private void WriteGammaChunk(Stream stream)
880875
// 4-byte unsigned integer of gamma * 100,000.
881876
uint gammaValue = (uint)(this.gamma * 100_000F);
882877

883-
BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), gammaValue);
878+
BinaryPrimitives.WriteUInt32BigEndian(this.chunkDataBuffer.Span.Slice(0, 4), gammaValue);
884879

885-
this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer, 0, 4);
880+
this.WriteChunk(stream, PngChunkType.Gamma, this.chunkDataBuffer.Span, 0, 4);
886881
}
887882
}
888883

@@ -899,7 +894,7 @@ private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata)
899894
return;
900895
}
901896

902-
Span<byte> alpha = this.chunkDataBuffer.AsSpan();
897+
Span<byte> alpha = this.chunkDataBuffer.Span;
903898
if (pngMetadata.ColorType == PngColorType.Rgb)
904899
{
905900
if (pngMetadata.TransparentRgb48.HasValue && this.use16Bit)
@@ -909,7 +904,7 @@ private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata)
909904
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G);
910905
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B);
911906

912-
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6);
907+
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 6);
913908
}
914909
else if (pngMetadata.TransparentRgb24.HasValue)
915910
{
@@ -918,21 +913,21 @@ private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata)
918913
alpha[1] = rgb.R;
919914
alpha[3] = rgb.G;
920915
alpha[5] = rgb.B;
921-
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6);
916+
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 6);
922917
}
923918
}
924919
else if (pngMetadata.ColorType == PngColorType.Grayscale)
925920
{
926921
if (pngMetadata.TransparentL16.HasValue && this.use16Bit)
927922
{
928923
BinaryPrimitives.WriteUInt16LittleEndian(alpha, pngMetadata.TransparentL16.Value.PackedValue);
929-
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2);
924+
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2);
930925
}
931926
else if (pngMetadata.TransparentL8.HasValue)
932927
{
933928
alpha.Clear();
934929
alpha[1] = pngMetadata.TransparentL8.Value.PackedValue;
935-
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2);
930+
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer.Span, 0, 2);
936931
}
937932
}
938933
}
@@ -1173,12 +1168,14 @@ private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data)
11731168
/// <param name="length">The of the data to write.</param>
11741169
private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data, int offset, int length)
11751170
{
1176-
BinaryPrimitives.WriteInt32BigEndian(this.buffer, length);
1177-
BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type);
1171+
Span<byte> buffer = stackalloc byte[8];
1172+
1173+
BinaryPrimitives.WriteInt32BigEndian(buffer, length);
1174+
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), (uint)type);
11781175

1179-
stream.Write(this.buffer, 0, 8);
1176+
stream.Write(buffer);
11801177

1181-
uint crc = Crc32.Calculate(this.buffer.AsSpan(4, 4)); // Write the type buffer
1178+
uint crc = Crc32.Calculate(buffer.Slice(4)); // Write the type buffer
11821179

11831180
if (data.Length > 0 && length > 0)
11841181
{
@@ -1187,9 +1184,9 @@ private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data, int o
11871184
crc = Crc32.Calculate(crc, data.Slice(offset, length));
11881185
}
11891186

1190-
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc);
1187+
BinaryPrimitives.WriteUInt32BigEndian(buffer, crc);
11911188

1192-
stream.Write(this.buffer, 0, 4); // write the crc
1189+
stream.Write(buffer, 0, 4); // write the crc
11931190
}
11941191

11951192
/// <summary>
@@ -1412,4 +1409,11 @@ private static PngBitDepth SuggestBitDepth<TPixel>()
14121409
Type t when t == typeof(RgbaVector) => PngBitDepth.Bit16,
14131410
_ => PngBitDepth.Bit8
14141411
};
1412+
1413+
private unsafe struct ScratchBuffer
1414+
{
1415+
private fixed byte scratch[16];
1416+
1417+
public Span<byte> Span => MemoryMarshal.CreateSpan(ref this.scratch[0], 16);
1418+
}
14151419
}

0 commit comments

Comments
 (0)