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
- Re-encode the APNG with ffmpeg/apngasm to regenerate correct fcTL chunks
- Re-download the file — the image may be corrupted in transit
- Catch PngIntegrityException and fall back to the PNG's static default image
- 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
- Verify downloaded files with checksums before decoding
- Run apngcheck on third-party APNG assets
- Cache a static first-frame fallback for every animated resource
- Reject files whose fcTL geometry exceeds the IHDR canvas at parse time
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
- Invalid first frame:
- Unsupported blendOp at frame
- Unsupported disposeOp at frame
- acTL chunk length must be
- fctl chunk expected sequence
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)