HMCL-dev/HMCL · error · PngIntegrityException

Invalid first frame:

Error message

Invalid first frame: 

What it means

Thrown by ImageUtils.toImage (APNG animation decoder) as a PngIntegrityException when the first APNG frame is not a full-size frame: its x/y offset is non-zero or its width/height differ from the PNG canvas dimensions. APNG spec requires frame 0 to cover the whole canvas.

Solutions

  1. Re-encode the APNG with a spec-compliant tool (ffmpeg, apngasm) so the first frame covers the full canvas
  2. Fall back to decoding the PNG as a static image (default image) when APNG decoding fails
  3. Catch PngIntegrityException and substitute a placeholder/static frame
  4. Validate the APNG with apngdis/apngcheck before feeding it to the decoder

Example fix

// before
Image image = ImageUtils.createAnimationFromAPNGApngStream(stream); // throws on bad first frame
// after
try {
    image = ImageUtils.createAnimationFromAPNGApngStream(stream);
} catch (PngIntegrityException e) {
    image = ImageUtils.scale(new Image(stream), width, height); // static fallback
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Parse fcTL of first frame before decoding
PngFrameControl first = frames.get(0).control();
boolean ok = first.xOffset == 0 && first.yOffset == 0
        && first.width == canvasWidth && first.height == canvasHeight;
if (!ok) { /* use static default image instead of animation */ }

Try / catch

try {
    image = ImageUtils.createAnimationFromAPNGApngStream(stream);
} catch (PngIntegrityException e) {
    image = staticFallbackImage;
}

Prevention

When it happens

Trigger: Decoding an APNG whose fcTL for frame index 0 declares xOffset/yOffset != 0 or a frame smaller than the IHDR canvas — i.e. a non-conformant APNG produced by a broken encoder.

Common situations: Malformed APNGs from buggy encoders or hand-edited PNG chunks; files that had fcTL chunks reordered or inserted incorrectly; multi-part images stitched by tools that ignored APNG constraints.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

                                 boolean doScale,
                                 int targetWidth, int targetHeight) throws PngException {
        final int width = sequence.header.width;
        final int height = sequence.header.height;

        List<Argb8888BitmapSequence.Frame> frames = sequence.getAnimationFrames();

        var framePixels = new int[frames.size()][];
        var durations = new int[framePixels.length];

        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,

View on GitHub (pinned to 24702dc5a0)