HMCL-dev/HMCL · error · PngIntegrityException
fctl chunk expected sequence
Error message
fctl chunk expected sequence %d but received %d
What it means
APNG chunks carry a monotonically increasing sequence number in fcTL (and fdAT) chunks so frames can be reassembled in order. readFrameControlChunk compares the chunk's sequence number against apngSequenceExpect and throws PngIntegrityException when they differ, because out-of-order or duplicated sequence numbers make frame reassembly ambiguous.
Solutions
- Re-acquire the APNG from the original source and verify its checksum.
- Re-encode the animation with apngasm/ffmpeg so sequence numbers are regenerated correctly.
- Renumber fcTL/fdAT sequence numbers with a repair tool before parsing.
- If intentional resumption is needed, reset the reader's expected sequence state (apngSequenceExpect) to match the stream start.
Example fix
// before: strict parse fails on renumbered file PngReadHelper.read(is, reader); // throws fctl chunk expected sequence 3 but received 7 // after: renumber sequence numbers first renumberApngSequences(file); // rewrite fcTL/fdAT seq from 0 PngReadHelper.read(is, reader);
Defensive patterns
Strategy: validation
Validate before calling
// walk chunks and assert fcTL sequence numbers start at 0 and increment
int expect = 0;
for (Chunk c : chunks(file)) {
if (c.type.equals("fcTL")) {
int seq = readIntBE(c.data, 0);
if (seq != expect) throw new IllegalArgumentException("fcTL seq " + seq + " != " + expect);
expect++;
}
} Try / catch
try {
PngReadHelper.read(is, apngReader);
} catch (PngIntegrityException e) {
LOG.warning("APNG sequence error, re-encoding: " + e.getMessage());
reencodeWithFfmpeg(file);
} Prevention
- Never manually reorder or splice fcTL chunks in APNG files.
- Validate sequence continuity when concatenating or editing APNGs programmatically.
- Use apngasm to disassemble/reassemble frames safely.
- Checksum-verify downloads before decoding.
When it happens
Trigger: Reading an APNG whose fcTL sequence number is out of order, skipped, duplicated, or starts at a value other than the expected 0 — typically in a corrupt or non-conformant file processed by readChunk → readFrameControlChunk.
Common situations: Files reordered/spliced by editing tools; partially overwritten downloads; APNGs assembled by scripts that renumber frames incorrectly; multiple fcTL chunks repeated after truncation/replay.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- fdAT chunk expected sequence
- fcTL chunk length must be
- tRNS chunk for greyscale image must be exactly length=2, not
- Illegal to have tRNS chunk with image type
- Invalid greyscale bit-depth
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/4dc01ff2290feff1.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/DefaultPngChunkReader.java:207
}
@Override
public void readAnimationControlChunk(PngSource source, int dataLength) throws IOException, PngException {
if (dataLength != PngConstants.LENGTH_acTL_CHUNK) {
throw new PngIntegrityException(String.format("acTL chunk length must be %d, not %d", PngConstants.LENGTH_acTL_CHUNK, dataLength));
}
processor.processAnimationControl(new PngAnimationControl(source.readInt(), source.readInt()));
}
@Override
public void readFrameControlChunk(PngSource source, int dataLength) throws IOException, PngException {
if (dataLength != PngConstants.LENGTH_fcTL_CHUNK) {
throw new PngIntegrityException(String.format("fcTL chunk length must be %d, not %d", PngConstants.LENGTH_fcTL_CHUNK, dataLength));
}
int sequence = source.readInt(); // TODO: check sequence # is correct or PngIntegrityException
if (sequence != apngSequenceExpect) {
throw new PngIntegrityException(String.format("fctl chunk expected sequence %d but received %d", apngSequenceExpect, sequence));
}
apngSequenceExpect++; // ready for next time
PngFrameControl frame = new PngFrameControl(
sequence,
source.readInt(), // width
source.readInt(), // height
source.readInt(), // x offset
source.readInt(), // y offset
source.readUnsignedShort(), // delay numerator
source.readUnsignedShort(), // delay denominator
source.readByte(), // dispose op
source.readByte() // blend op
);
if (sequence == 0) { // We're at the first frame...
if (idatCount == 0) { // Not seen any IDAT chunks yet
// APNG Spec says that when the first fcTL chunk is received *before* the first IDAT chunkView on GitHub (pinned to 24702dc5a0)