gchq/CyberChef · error · OperationError

Error opening image. (${err})

Error message

Error opening image. (${err})

What it means

Thrown by parseQrCode when `Jimp.read(input)` rejects — the input could not be decoded as a raster image. The underlying Jimp error is appended in parentheses. It is an OperationError surfaced to the recipe.

Source

Thrown at src/core/lib/QRCode.mjs:27

import OperationError from "../errors/OperationError.mjs";
import jsQR from "jsqr";
import qr from "qr-image";
import Utils from "../Utils.mjs";
import { Jimp, JimpMime } from "jimp";

/**
 * Parses a QR code image from an image
 *
 * @param {ArrayBuffer} input
 * @param {boolean} normalise
 * @returns {string}
 */
export async function parseQrCode(input, normalise) {
    let image;
    try {
        image = await Jimp.read(input);
    } catch (err) {
        throw new OperationError(`Error opening image. (${err})`);
    }

    try {
        if (normalise) {
            image.greyscale();
            image.normalize();
        }
    } catch (err) {
        throw new OperationError(`Error normalising image. (${err})`);
    }

    // Remove transparency which jsQR cannot handle
    image.scan((x, y, idx) => {
        // If pixel is fully transparent, make it opaque white
        if (image.bitmap.data[idx + 3] === 0x00) {
            image.bitmap.data[idx + 0] = 0xff;
            image.bitmap.data[idx + 1] = 0xff;
            image.bitmap.data[idx + 2] = 0xff;

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Ensure the input is a complete JPEG, PNG, or BMP ArrayBuffer.
  2. Pre-validate the magic bytes (e.g. PNG \x89PNG, JPEG \xff\xd8) before calling parseQrCode.
  3. If the source is SVG/WebP/HEIC, rasterise to PNG first.

Example fix

// before
await parseQrCode(textToArrayBuffer('not an image'), true);
// after
await parseQrCode(pngArrayBuffer, true);
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeImage(buf) {
  const b = new Uint8Array(buf);
  return (b[0] === 0x89 && b[1] === 0x50) // PNG
      || (b[0] === 0xff && b[1] === 0xd8) // JPEG
      || (b[0] === 0x42 && b[1] === 0x4d); // BMP
}
if (!looksLikeImage(input)) throw new Error('Input is not a supported image');
await parseQrCode(input, normalise);

Type guard

function isSupportedImageBuffer(buf) {
  const b = new Uint8Array(buf);
  return (b[0] === 0x89 && b[1] === 0x50) || (b[0] === 0xff && b[1] === 0xd8) || (b[0] === 0x42 && b[1] === 0x4d);
}

Try / catch

try {
  return await parseQrCode(input, normalise);
} catch (e) {
  if (e instanceof OperationError && /Error opening image/.test(e.message)) {
    // convert/re-encode input to PNG before retrying
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling parseQrCode with an ArrayBuffer that is not a supported image format (Jimp supports JPEG, PNG, BMP, TIFF, GIF), a truncated/corrupt file, an empty buffer, or plain text bytes.

Common situations: Input not yet converted to an image (still raw bytes / SVG / WebP unsupported by the Jimp build); file truncated in transit; wrong MIME assumption; very large image exhausting memory.

Related errors


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