Skip to content

Commit ac19053

Browse files
Fix Identify returning incorrect frame count for animated PNGs
The Identify method had two bugs when processing fdAT (FrameData) chunks: 1. A spurious Skip(4) before SkipChunkDataAndCrc caused the stream to be misaligned by 4 bytes, since chunk.Length already includes the 4-byte sequence number. 2. Unlike Decode, which consumes all fdAT chunks for a frame in one shot via ReadScanlines + ReadNextFrameDataChunk, Identify processed them individually, calling InitializeFrameMetadata for each chunk and inflating the frame count. The fix removes the extra Skip(4) and adds SkipRemainingFrameDataChunks to consume all continuation fdAT chunks for a frame, mirroring how ReadNextFrameDataChunk works during decoding.
1 parent bd19151 commit ac19053

4 files changed

Lines changed: 44 additions & 2 deletions

File tree

src/ImageSharp/Formats/Png/PngDecoderCore.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,9 +428,10 @@ protected override ImageInfo Identify(BufferedReadStream stream, CancellationTok
428428

429429
InitializeFrameMetadata(framesMetadata, currentFrameControl.Value);
430430

431-
// Skip sequence number
432-
this.currentStream.Skip(4);
431+
// Skip data for this and all remaining FrameData chunks belonging to the same frame
432+
// (comparable to how Decode consumes them via ReadScanlines + ReadNextFrameDataChunk).
433433
this.SkipChunkDataAndCrc(chunk);
434+
this.SkipRemainingFrameDataChunks(buffer);
434435
break;
435436
case PngChunkType.Data:
436437

@@ -2093,6 +2094,31 @@ private int ReadNextFrameDataChunk()
20932094
return 0;
20942095
}
20952096

2097+
/// <summary>
2098+
/// Skips any remaining <see cref="PngChunkType.FrameData"/> chunks belonging to the current frame.
2099+
/// This mirrors how <see cref="ReadNextFrameDataChunk"/> is used during decoding:
2100+
/// consecutive fdAT chunks are consumed until a non-fdAT chunk is encountered,
2101+
/// which is stored in <see cref="nextChunk"/> for the next iteration.
2102+
/// </summary>
2103+
/// <param name="buffer">Temporary buffer.</param>
2104+
private void SkipRemainingFrameDataChunks(Span<byte> buffer)
2105+
{
2106+
while (this.TryReadChunk(buffer, out PngChunk chunk))
2107+
{
2108+
if (chunk.Type is PngChunkType.FrameData)
2109+
{
2110+
chunk.Data?.Dispose();
2111+
this.SkipChunkDataAndCrc(chunk);
2112+
}
2113+
else
2114+
{
2115+
// Not a FrameData chunk; store it so the next TryReadChunk call returns it.
2116+
this.nextChunk = chunk;
2117+
return;
2118+
}
2119+
}
2120+
}
2121+
20962122
/// <summary>
20972123
/// Reads a chunk from the stream.
20982124
/// </summary>

tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,18 @@ public void Identify_IgnoreCrcErrors(string imagePath, int expectedPixelSize)
411411
Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel);
412412
}
413413

414+
[Fact]
415+
public void Identify_AnimatedPng_ReadsFrameCountCorrectly()
416+
{
417+
TestFile testFile = TestFile.Create(TestImages.Png.AnimatedFrameCount);
418+
419+
using MemoryStream stream = new(testFile.Bytes, false);
420+
ImageInfo imageInfo = Image.Identify(stream);
421+
422+
Assert.NotNull(imageInfo);
423+
Assert.Equal(50, imageInfo.FrameMetadataCollection.Count);
424+
}
425+
414426
[Theory]
415427
[WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)]
416428
public void Decode_MissingDataChunk_ThrowsException<TPixel>(TestImageProvider<TPixel> provider)

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public static class Png
7676
public const string BlendOverMultiple = "Png/animated/21-blend-over-multiple.png";
7777
public const string FrameOffset = "Png/animated/frame-offset.png";
7878
public const string DefaultNotAnimated = "Png/animated/default-not-animated.png";
79+
public const string AnimatedFrameCount = "Png/animated/issue-animated-frame-count.png";
7980
public const string Issue2666 = "Png/issues/Issue_2666.png";
8081
public const string Issue2882 = "Png/issues/Issue_2882.png";
8182

Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)