HMCL-dev/HMCL · error · PngIntegrityException

Unsupported blendOp at frame

Error message

Unsupported blendOp  at frame 

What it means

Thrown by ImageUtils.toImage as PngIntegrityException when an APNG frame's blend_op is neither APNG_BLEND_OP_SOURCE (0) nor APNG_BLEND_OP_OVER (1). Only those two blend operations are defined by the APNG spec; anything else means a corrupt/invalid chunk.

Solutions

  1. Re-encode the animation with a standard tool so blend_op is 0 or 1
  2. Replace the file (corruption is the usual cause — no encoder legitimately emits other values)
  3. Catch PngIntegrityException and fall back to a static image or placeholder
  4. Sanity-check chunk fields during parse and reject non-spec APNGs early

Example fix

// before
Image anim = ImageUtils.createAnimationFromAPNGApngStream(in);
// after
try {
    anim = ImageUtils.createAnimationFromAPNGApngStream(in);
} catch (PngIntegrityException e) {
    LOG.warning("Unsupported APNG feature: " + e.getMessage());
    anim = fallbackStaticImage;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (control.blendOp != 0 && control.blendOp != 1) {
    throw new IllegalArgumentException("Non-spec blendOp: " + control.blendOp);
}

Type guard

static boolean hasKnownBlendOp(PngFrameControl c) {
    return c.blendOp == 0 || c.blendOp == 1; // SOURCE or OVER
}

Try / catch

try {
    return ImageUtils.toImage(sequence, w, h, ...);
} catch (PngIntegrityException e) {
    return defaultImage; // static fallback
}

Prevention

When it happens

Trigger: Decoding an APNG whose fcTL chunk carries a blend_op value other than 0 or 1 (e.g. 2+ from a corrupted or maliciously crafted file).

Common situations: Bit-rot or corruption in stored APNG files; crafted test images; outputs of experimental encoders.

Related errors


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

Appendix: source

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

                            if (outAlpha == 0) {
                                outR = outG = outB = 0;
                            } else {
                                outR = (srcR * srcAlpha + dstR * dstAlpha * invSrcAlpha / 255 + outAlpha / 2) / outAlpha;
                                outG = (srcG * srcAlpha + dstG * dstAlpha * invSrcAlpha / 255 + outAlpha / 2) / outAlpha;
                                outB = (srcB * srcAlpha + dstB * dstAlpha * invSrcAlpha / 255 + outAlpha / 2) / outAlpha;
                            }

                            outAlpha = Math.min(outAlpha, 255);
                            outR = Math.min(outR, 255);
                            outG = Math.min(outG, 255);
                            outB = Math.min(outB, 255);

                            currentFrameBuffer[dstIndex] = (outAlpha << 24) | (outR << 16) | (outG << 8) | outB;
                        }
                    }
                }
            } else {
                throw new PngIntegrityException("Unsupported blendOp " + control.blendOp + " at frame " + frameIndex);
            }

            if (doScale)
                framePixels[frameIndex] = scale(currentFrameBuffer,
                        width, height,
                        targetWidth, targetHeight);
            else
                framePixels[frameIndex] = currentFrameBuffer;

            if (control.delayNumerator == 0) {
                durations[frameIndex] = 10;
            } else {
                int durationsMills = 1000 * control.delayNumerator;
                if (control.delayDenominator == 0)
                    durationsMills /= 100;
                else
                    durationsMills /= control.delayDenominator;

View on GitHub (pinned to 24702dc5a0)