HMCL-dev/HMCL · error · PngException

ERROR_NOT_PNG

ERROR_NOT_PNG

Error message

Failed to read PNG signature

What it means

PngReadHelper.read requires the input stream to begin with the 8-byte PNG signature. readSignature returns false when the leading bytes do not match, and read throws PngException with code ERROR_NOT_PNG, meaning the stream is not a PNG at all.

Solutions

  1. Check the first bytes of the stream/file — they must be 89 50 4E 47 0D 0A 1A 0A.
  2. Unwrap any compression (e.g. GZIPInputStream) before passing the stream.
  3. Verify the URL/download actually returned a PNG (check Content-Type and HTTP status).
  4. Handle PngException with code ERROR_NOT_PNG to give the user a clear 'not a PNG' message.

Example fix

// before: passing raw (possibly gzipped or HTML) stream
PngReadHelper.read(is, reader); // Failed to read PNG signature
// after
if (!is.markSupported()) is = new BufferedInputStream(is);
is.mark(8);
byte[] head = is.readNBytes(2);
is.reset();
if ((head[0] & 0xFF) == 0x1F && (head[1] & 0xFF) == 0x8B) is = new GZIPInputStream(is);
PngReadHelper.read(is, reader);
Defensive patterns

Strategy: validation

Validate before calling

// check PNG signature before decoding
static boolean isPng(InputStream is) throws IOException {
  is.mark(8);
  byte[] sig = is.readNBytes(8);
  is.reset();
  return java.util.Arrays.equals(sig,
      new byte[]{(byte)0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A});
}

Try / catch

try {
  PngReadHelper.read(is, reader);
} catch (PngException e) {
  if (e.getCode() == PngConstants.ERROR_NOT_PNG) {
    showNotAPngMessage();
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a non-PNG stream (JPEG, WebP, HTML error page, empty file, gzip-compressed PNG not decompressed first) as the InputStream to PngReadHelper.read.

Common situations: Wrong file extension/content confusion after a download; server returning an HTML error page with 200; serving .png.gz without decompression; concatenating files; testing with placeholder files.

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/ea34b8a20162d664. Report an issue: GitHub.

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngReadHelper.java:57

            }
        }
        return true;
    }

    /**
     * Reads a given InputStream using the PngReader to process all chunks until the file is
     * finished, then returning the result from the PngReader.
     *
     * @param is        stream to read
     * @param reader    reads and delegates processing of all chunks
     * @param <ResultT> result of the processing
     * @return result of the processing of the InputStream.
     * @throws PngException
     */
    public static <ResultT> ResultT read(InputStream is, PngReader<ResultT> reader) throws PngException {
        try {
            if (!PngReadHelper.readSignature(is)) {
                throw new PngException(PngConstants.ERROR_NOT_PNG, "Failed to read PNG signature");
            }

//            PngAtOnceSource source = PngAtOnceSource.from(is);//, sourceName);
            PngSource source = new PngStreamSource(is);
            boolean finished = false;

            while (!finished) {
                int length = source.readInt();
                int code = source.readInt();
                finished = reader.readChunk(source, code, length);
            }

            if (source.available() > 0) { // Should trailing data after IEND always be error or can configure as warning?
                throw new PngException(PngConstants.ERROR_EOF_EXPECTED, String.format("Completed IEND but %d byte(s) remain", source.available()));
            }

            reader.finishedChunks(source);

View on GitHub (pinned to 24702dc5a0)