Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 47 additions & 31 deletions src/ImageSharp/Formats/Png/PngDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,9 @@ private void ProcessInterlacedDefilteredScanline<TPixel>(
/// <param name="pngMetadata">The png metadata.</param>
private static void AssignColorPalette(ReadOnlySpan<byte> palette, ReadOnlySpan<byte> alpha, PngMetadata pngMetadata)
{
if (palette.Length == 0)
// A palette chunk too short to hold a single entry would produce an empty color table, which the
// scanline processors would index outside of. Treat it as missing so indexed decoding reports it.
if (palette.Length < Unsafe.SizeOf<Rgb24>())
{
return;
}
Expand Down Expand Up @@ -1380,9 +1382,11 @@ private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha, PngMetadata pngM
{
if (this.header.BitDepth == 16)
{
ushort rc = BinaryPrimitives.ReadUInt16LittleEndian(alpha[..2]);
ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2));
ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2));
// tRNS samples are stored big-endian like all other PNG multi-byte values,
// matching the byte order used for the 16-bit samples in the image data.
ushort rc = BinaryPrimitives.ReadUInt16BigEndian(alpha[..2]);
ushort gc = BinaryPrimitives.ReadUInt16BigEndian(alpha.Slice(2, 2));
ushort bc = BinaryPrimitives.ReadUInt16BigEndian(alpha.Slice(4, 2));

pngMetadata.TransparentColor = Color.FromPixel(new Rgb48(rc, gc, bc));
return;
Expand All @@ -1400,7 +1404,7 @@ private void AssignTransparentMarkers(ReadOnlySpan<byte> alpha, PngMetadata pngM
{
if (this.header.BitDepth == 16)
{
pngMetadata.TransparentColor = Color.FromPixel(new L16(BinaryPrimitives.ReadUInt16LittleEndian(alpha[..2])));
pngMetadata.TransparentColor = Color.FromPixel(new L16(BinaryPrimitives.ReadUInt16BigEndian(alpha[..2])));
}
else
{
Expand Down Expand Up @@ -2183,24 +2187,30 @@ private int ReadNextDataChunk()

Span<byte> buffer = stackalloc byte[20];

int length = this.currentStream.Read(buffer, 0, 4);
if (length == 0)
// The compressed stream is the concatenation of the payloads of all consecutive data chunks, and the
// specification permits zero-length data chunks. A return value of 0 tells the inflater that the
// compressed stream has ended, so empty chunks are skipped rather than reported.
while (true)
{
return 0;
}
// Skip the CRC of the previous chunk.
int length = this.currentStream.Read(buffer, 0, 4);
if (length == 0 || !this.TryReadChunk(buffer, out PngChunk chunk))
{
return 0;
}

if (this.TryReadChunk(buffer, out PngChunk chunk))
{
if (chunk.Type is PngChunkType.Data or PngChunkType.FrameData)
if (chunk.Type is not (PngChunkType.Data or PngChunkType.FrameData))
{
chunk.Data?.Dispose();
return chunk.Length;
this.nextChunk = chunk;
return 0;
}

this.nextChunk = chunk;
chunk.Data?.Dispose();
if (chunk.Length > 0)
{
return chunk.Length;
}
}

return 0;
}

/// <summary>
Expand All @@ -2216,26 +2226,32 @@ private int ReadNextFrameDataChunk()

Span<byte> buffer = stackalloc byte[20];

int length = this.currentStream.Read(buffer, 0, 4);
if (length == 0)
{
return 0;
}

if (this.TryReadChunk(buffer, out PngChunk chunk))
// As with data chunks, frame data chunks with no payload after the sequence number are valid and are
// skipped, so that a return value of 0 only ever means that the frame's compressed stream has ended.
while (true)
{
if (chunk.Type is PngChunkType.FrameData)
// Skip the CRC of the previous chunk.
int length = this.currentStream.Read(buffer, 0, 4);
if (length == 0 || !this.TryReadChunk(buffer, out PngChunk chunk))
{
chunk.Data?.Dispose();
return 0;
}

this.currentStream.Position += 4; // Skip sequence number
return chunk.Length - 4;
if (chunk.Type is not PngChunkType.FrameData)
{
this.nextChunk = chunk;
return 0;
}

this.nextChunk = chunk;
}
chunk.Data?.Dispose();

return 0;
this.currentStream.Position += 4; // Skip sequence number
int dataLength = chunk.Length - 4;
if (dataLength > 0)
{
return dataLength;
}
}
}

/// <summary>
Expand Down
9 changes: 5 additions & 4 deletions src/ImageSharp/Formats/Png/PngEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1247,10 +1247,11 @@ private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata)
{
if (this.use16Bit)
{
// tRNS samples are stored big-endian like all other PNG multi-byte values.
Rgb48 rgb = pngMetadata.TransparentColor.Value.ToPixel<Rgb48>();
BinaryPrimitives.WriteUInt16LittleEndian(alpha, rgb.R);
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(2, 2), rgb.G);
BinaryPrimitives.WriteUInt16LittleEndian(alpha.Slice(4, 2), rgb.B);
BinaryPrimitives.WriteUInt16BigEndian(alpha, rgb.R);
BinaryPrimitives.WriteUInt16BigEndian(alpha.Slice(2, 2), rgb.G);
BinaryPrimitives.WriteUInt16BigEndian(alpha.Slice(4, 2), rgb.B);

this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 6);
}
Expand All @@ -1269,7 +1270,7 @@ private void WriteTransparencyChunk(Stream stream, PngMetadata pngMetadata)
if (this.use16Bit)
{
L16 l16 = pngMetadata.TransparentColor.Value.ToPixel<L16>();
BinaryPrimitives.WriteUInt16LittleEndian(alpha, l16.PackedValue);
BinaryPrimitives.WriteUInt16BigEndian(alpha, l16.PackedValue);
this.WriteChunk(stream, PngChunkType.Transparency, this.chunkDataBuffer, 0, 2);
}
else
Expand Down
Loading