HMCL-dev/HMCL · error · PngIntegrityException

Invalid frame control:

Error message

Invalid frame control: 

What it means

Thrown by ImageUtils.toImage as PngIntegrityException when an APNG frame's fcTL control values are out of bounds: negative offsets/delays, a frame extending past the canvas, or invalid canvas dimensions. It guards the framebuffer from out-of-range writes.

Solutions

  1. Re-encode the APNG with ffmpeg/apngasm to regenerate correct fcTL chunks
  2. Re-download the file — the image may be corrupted in transit
  3. Catch PngIntegrityException and fall back to the PNG's static default image
  4. Pre-validate frame geometry against the IHDR canvas before decoding

Example fix

// before
frames.forEach(f -> decode(f)); // throws on bad control
// after
if (frames.stream().allMatch(f -> validControl(f.control(), width, height))) {
    decodeAll(frames);
} else {
    useStaticImage();
}
Defensive patterns

Strategy: validation

Validate before calling

static boolean frameInBounds(PngFrameControl c, int w, int h) {
    return c.xOffset >= 0 && c.yOffset >= 0
        && c.xOffset + c.width <= w && c.yOffset + c.height <= h
        && c.delayNumerator >= 0 && c.delayDenominator >= 0;
}

Try / catch

try {
    animation = ImageUtils.toImage(sequence, ...);
} catch (PngIntegrityException e) {
    animation = staticImage;
    LOG.warning("Corrupt APNG frame control: " + e.getMessage());
}

Prevention

When it happens

Trigger: Decoding an APNG where any frame's xOffset/width exceeds the canvas (xOffset + width > width, same for y/height), or delayNumerator/delayDenominator are negative — a corrupt or non-conformant fcTL chunk.

Common situations: Corrupt downloads/truncated APNG files; broken third-party APNG encoders writing bad chunk fields; hand-crafted or fuzzed PNG inputs.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/c951b37a2bea585b. Report an issue: GitHub.

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java:603

        int[] buffer = new int[Math.multiplyExact(width, height)];
        for (int frameIndex = 0; frameIndex < frames.size(); frameIndex++) {
            var frame = frames.get(frameIndex);
            PngFrameControl control = frame.control();

            if (frameIndex == 0 && (
                    control.xOffset != 0 || control.yOffset != 0
                            || control.width != width || control.height != height)) {
                throw new PngIntegrityException("Invalid first frame: " + control);
            }

            if (control.xOffset < 0 || control.yOffset < 0
                    || width < 0 || height < 0
                    || control.xOffset + control.width > width
                    || control.yOffset + control.height > height
                    || control.delayNumerator < 0 || control.delayDenominator < 0
            ) {
                throw new PngIntegrityException("Invalid frame control: " + control);
            }

            int[] currentFrameBuffer = buffer.clone();
            if (control.blendOp == 0) {
                for (int row = 0; row < control.height; row++) {
                    System.arraycopy(frame.bitmap().array(),
                            row * control.width,
                            currentFrameBuffer,
                            (control.yOffset + row) * width + control.xOffset,
                            control.width);
                }
            } else if (control.blendOp == 1) {
                // APNG_BLEND_OP_OVER - Alpha blending
                for (int row = 0; row < control.height; row++) {
                    for (int col = 0; col < control.width; col++) {
                        int srcIndex = row * control.width + col;
                        int dstIndex = (control.yOffset + row) * width + control.xOffset + col;

View on GitHub (pinned to 24702dc5a0)