{"record":{"id":"274aa8225ca09fce","repo":"HMCL-dev/HMCL","slug":"invalid-first-frame","errorCode":null,"errorMessage":"Invalid first frame: ","messagePattern":"Invalid first frame: ","errorType":"validation","errorClass":"PngIntegrityException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java","lineNumber":594,"sourceCode":"                                 boolean doScale,\n                                 int targetWidth, int targetHeight) throws PngException {\n        final int width = sequence.header.width;\n        final int height = sequence.header.height;\n\n        List<Argb8888BitmapSequence.Frame> frames = sequence.getAnimationFrames();\n\n        var framePixels = new int[frames.size()][];\n        var durations = new int[framePixels.length];\n\n        int[] buffer = new int[Math.multiplyExact(width, height)];\n        for (int frameIndex = 0; frameIndex < frames.size(); frameIndex++) {\n            var frame = frames.get(frameIndex);\n            PngFrameControl control = frame.control();\n\n            if (frameIndex == 0 && (\n                    control.xOffset != 0 || control.yOffset != 0\n                            || control.width != width || control.height != height)) {\n                throw new PngIntegrityException(\"Invalid first frame: \" + control);\n            }\n\n            if (control.xOffset < 0 || control.yOffset < 0\n                    || width < 0 || height < 0\n                    || control.xOffset + control.width > width\n                    || control.yOffset + control.height > height\n                    || control.delayNumerator < 0 || control.delayDenominator < 0\n            ) {\n                throw new PngIntegrityException(\"Invalid frame control: \" + control);\n            }\n\n            int[] currentFrameBuffer = buffer.clone();\n            if (control.blendOp == 0) {\n                for (int row = 0; row < control.height; row++) {\n                    System.arraycopy(frame.bitmap().array(),\n                            row * control.width,\n                            currentFrameBuffer,\n                            (control.yOffset + row) * width + control.xOffset,","sourceCodeStart":576,"sourceCodeEnd":612,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/ImageUtils.java#L576-L612","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-encode the APNG with a spec-compliant tool (ffmpeg, apngasm) so the first frame covers the full canvas","Fall back to decoding the PNG as a static image (default image) when APNG decoding fails","Catch PngIntegrityException and substitute a placeholder/static frame","Validate the APNG with apngdis/apngcheck before feeding it to the decoder"],"exampleFix":"// before\nImage image = ImageUtils.createAnimationFromAPNGApngStream(stream); // throws on bad first frame\n// after\ntry {\n    image = ImageUtils.createAnimationFromAPNGApngStream(stream);\n} catch (PngIntegrityException e) {\n    image = ImageUtils.scale(new Image(stream), width, height); // static fallback\n}","handlingStrategy":"try-catch","validationCode":"// Parse fcTL of first frame before decoding\nPngFrameControl first = frames.get(0).control();\nboolean ok = first.xOffset == 0 && first.yOffset == 0\n        && first.width == canvasWidth && first.height == canvasHeight;\nif (!ok) { /* use static default image instead of animation */ }","typeGuard":null,"tryCatchPattern":"try {\n    image = ImageUtils.createAnimationFromAPNGApngStream(stream);\n} catch (PngIntegrityException e) {\n    image = staticFallbackImage;\n}","preventionTips":["Re-encode APNGs with ffmpeg/apngasm rather than hand-editing chunks","Treat APNG decode failures as 'use default image' events, not crashes","Validate third-party APNG assets once at build/import time","Never reorder or insert fcTL chunks manually"],"tags":["apng","png-validation","image-decoding"],"backgroundTag":"schema-validation-failed","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}