{"record":{"id":"dcb46a9048edcd30","repo":"gchq/CyberChef","slug":"error-opening-image-err","errorCode":null,"errorMessage":"Error opening image. (${err})","messagePattern":"Error opening image\\. \\((.+?)\\)","errorType":"exception","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/lib/QRCode.mjs","lineNumber":27,"sourceCode":"import OperationError from \"../errors/OperationError.mjs\";\nimport jsQR from \"jsqr\";\nimport qr from \"qr-image\";\nimport Utils from \"../Utils.mjs\";\nimport { Jimp, JimpMime } from \"jimp\";\n\n/**\n * Parses a QR code image from an image\n *\n * @param {ArrayBuffer} input\n * @param {boolean} normalise\n * @returns {string}\n */\nexport async function parseQrCode(input, normalise) {\n    let image;\n    try {\n        image = await Jimp.read(input);\n    } catch (err) {\n        throw new OperationError(`Error opening image. (${err})`);\n    }\n\n    try {\n        if (normalise) {\n            image.greyscale();\n            image.normalize();\n        }\n    } catch (err) {\n        throw new OperationError(`Error normalising image. (${err})`);\n    }\n\n    // Remove transparency which jsQR cannot handle\n    image.scan((x, y, idx) => {\n        // If pixel is fully transparent, make it opaque white\n        if (image.bitmap.data[idx + 3] === 0x00) {\n            image.bitmap.data[idx + 0] = 0xff;\n            image.bitmap.data[idx + 1] = 0xff;\n            image.bitmap.data[idx + 2] = 0xff;","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/lib/QRCode.mjs#L9-L45","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the input is a complete JPEG, PNG, or BMP ArrayBuffer.","Pre-validate the magic bytes (e.g. PNG \\x89PNG, JPEG \\xff\\xd8) before calling parseQrCode.","If the source is SVG/WebP/HEIC, rasterise to PNG first."],"exampleFix":"// before\nawait parseQrCode(textToArrayBuffer('not an image'), true);\n// after\nawait parseQrCode(pngArrayBuffer, true);","handlingStrategy":"validation","validationCode":"function looksLikeImage(buf) {\n  const b = new Uint8Array(buf);\n  return (b[0] === 0x89 && b[1] === 0x50) // PNG\n      || (b[0] === 0xff && b[1] === 0xd8) // JPEG\n      || (b[0] === 0x42 && b[1] === 0x4d); // BMP\n}\nif (!looksLikeImage(input)) throw new Error('Input is not a supported image');\nawait parseQrCode(input, normalise);","typeGuard":"function isSupportedImageBuffer(buf) {\n  const b = new Uint8Array(buf);\n  return (b[0] === 0x89 && b[1] === 0x50) || (b[0] === 0xff && b[1] === 0xd8) || (b[0] === 0x42 && b[1] === 0x4d);\n}","tryCatchPattern":"try {\n  return await parseQrCode(input, normalise);\n} catch (e) {\n  if (e instanceof OperationError && /Error opening image/.test(e.message)) {\n    // convert/re-encode input to PNG before retrying\n  }\n  throw e;\n}","preventionTips":["Check magic bytes before calling parseQrCode.","Keep CyberChef/Jimp on supported formats (PNG/JPEG/BMP).","Rasterise SVG/WebP/HEIC to PNG upstream."],"tags":["qrcode","image","jimp","validation","operation-error"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}