HMCL-dev/HMCL · error · PngIntegrityException

Invalid palette data length

Error message

Invalid palette data length: %d (not a multiple of 3)

What it means

This validation in PngPalette.from fires when a PLTE chunk's data length is not divisible by 3. Each palette entry is exactly 3 bytes (R, G, B), so a length that is not a multiple of 3 means the chunk is truncated or corrupt and the palette cannot be parsed into whole RGB triplets.

Solutions

  1. Re-encode or re-save the PNG with a compliant tool to regenerate a valid PLTE.
  2. Verify the chunk length against the file's chunk table; check for truncation in transfer.
  3. Catch PngIntegrityException and reject the image as corrupt.

Example fix

// before
PngPalette.from(source, first, length);
// after
if (length % 3 != 0) {
    throw new PngIntegrityException("PLTE length " + length + " not multiple of 3");
}
PngPalette.from(source, first, length);
Defensive patterns

Strategy: try-catch

Validate before calling

if (plteLength % 3 != 0) throw new IllegalArgumentException("PLTE length not multiple of 3");

Try / catch

try { PngPalette.from(source, first, length); } catch (PngIntegrityException e) { handleCorruptPalette(e); }

Prevention

When it happens

Trigger: A PLTE chunk whose dataLength is not a multiple of 3, from truncation, corruption, or a broken encoder.

Common situations: Corrupted downloads, files edited with hex editors, fuzzed test inputs, faulty custom PNG writers.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/chunks/PngPalette.java:40

 */
public class PngPalette {
    // TODO: should include alpha here? Can then store as int32s?
    public final byte[] rgb888;
    public final int[] rgba8888; // Including this duplicate for now. Not sure if will keep it.
    public final int numColours;

    public static final int LENGTH_RGB_BYTES = 3;
    public static final int BYTE_INITIAL_ALPHA = 0xff;

    public PngPalette(byte[] rgb888, int[] rgba8888) {
        this.rgb888 = rgb888;
        this.rgba8888 = rgba8888;
        this.numColours = rgb888.length / 3;
    }

    public static PngPalette from(byte[] source, int first, int length) throws PngException {
        if (length % LENGTH_RGB_BYTES != 0) {
            throw new PngIntegrityException(String.format("Invalid palette data length: %d (not a multiple of 3)", length));
        }

        return new PngPalette(
                Arrays.copyOfRange(source, first, first + length),
                rgba8888From(source, first, length)
        );
    }

    private static int[] rgba8888From(byte[] source, int first, int length) {
        int last = first + length;
        int numColours = length / 3;
        int[] rgba8888 = new int[numColours];
        int j = 0;
        for (int i = first; i < last; i += LENGTH_RGB_BYTES) {
            rgba8888[j] = source[i] << 24 | source[i + 1] << 16 | source[i + 2] << 8 | BYTE_INITIAL_ALPHA;
            j++;
        }
        return rgba8888;

View on GitHub (pinned to 24702dc5a0)