gchq/CyberChef · error · OperationError

Error loading image. (${err})

Error message

Error loading image. (${err})

What it means

Thrown from the catch block wrapping Jimp.read(input) in DitherImage.run. CyberChef first passes the input through isImage() (a magic-byte sniff), which only checks a file signature and does not validate structural integrity. Jimp.read then fully decodes the bytes, and when decoding fails the underlying Jimp/js-binary-parser error is re-thrown as an OperationError 'Error loading image. (${err})'. The placeholder interpolates Jimp's own message, which usually names the codec that failed (PNG/JPEG/BMP/etc.).

Source

Thrown at src/core/operations/DitherImage.mjs:48

        this.presentType = "html";
        this.args = [];
    }

    /**
     * @param {ArrayBuffer} input
     * @param {Object[]} args
     * @returns {byteArray}
     */
    async run(input, args) {
        if (!isImage(input)) {
            throw new OperationError("Invalid file type.");
        }

        let image;
        try {
            image = await Jimp.read(input);
        } catch (err) {
            throw new OperationError(`Error loading image. (${err})`);
        }
        try {
            if (isWorkerEnvironment())
                self.sendStatusMessage("Applying dither to image...");
            image.dither();

            let imageBuffer;
            if (image.mime === "image/gif") {
                imageBuffer = await image.getBuffer(JimpMime.png);
            } else {
                imageBuffer = await image.getBuffer(image.mime);
            }
            return imageBuffer.buffer;
        } catch (err) {
            throw new OperationError(
                `Error applying dither to image. (${err})`,
            );
        }

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Verify the input decodes in an external viewer before feeding DitherImage; if not, fix the source upstream.
  2. If bytes come from a previous operation, inspect that step's output (add Hex dump / Render image) to confirm the body survived.
  3. Ensure the input is one of the formats Jimp decodes (PNG, JPEG, BMP, GIF, TIFF) rather than trusting the magic-byte sniff.
  4. Re-encode the source image to PNG with an external tool and retry.

Example fix

// before: bytes that only share a header
run(corruptedBytes, []);
// after: structurally valid PNG
run(await reencodeToPng(originalBytes), []);
Defensive patterns

Strategy: validation

Validate before calling

// Validate the image decodes fully before DitherImage.
import Jimp from "jimp";
async function isDecodableImage(bytes) {
  try { await Jimp.read(Buffer.from(bytes)); return true; }
  catch { return false; }
}
if (!(await isDecodableImage(input))) throw new Error("input is not a decodable image");

Try / catch

try {
  const out = await dither.run(input, args);
} catch (e) {
  if (String(e).includes("Error loading image")) { /* bad input - do not retry */ }
  else throw e;
}

Prevention

When it happens

Trigger: Input passes isImage() because its magic bytes match a recognised image signature, but the file is truncated, corrupted, or has a valid header with malformed pixel data. Also a non-image sharing an image magic number, a zero-length body after a valid header, or a format Jimp's bundled decoders cannot parse (e.g. some TIFF/HEIF variants) reaches Jimp.read and throws.

Common situations: Pasting a base64 image whose payload was clipped; a progressive JPEG cut mid-scan; chaining an operation that mutates bytes before DitherImage so the signature survives but the body does not; a very new/obscure container that shares PNG/JPEG magic but is outside Jimp's supported set.

Related errors


AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13). Data as JSON: /api/errors/9b324bf581a7bb72. Report an issue: GitHub.