{"record":{"id":"aab92b2e3daa62a1","repo":"mozilla/pdf.js","slug":"reading-gray-scale-from-a-color-image-numcomps","errorCode":null,"errorMessage":"Reading gray scale from a color image: ${numComps}","messagePattern":"Reading gray scale from a color image: (.+?)","errorType":"exception","errorClass":"FormatError","httpStatus":null,"severity":"error","filePath":"src/core/image.js","lineNumber":1019,"sourceCode":"    buffer,\n    {\n      destWidth,\n      destHeight,\n      invertOutput,\n      maxRows,\n      offset = 0,\n      stride = 1,\n    } = {}\n  ) {\n    if (typeof PDFJSDev === \"undefined\" || PDFJSDev.test(\"TESTING\")) {\n      assert(\n        buffer instanceof Uint8ClampedArray,\n        'PDFImage.fillGrayBuffer: Unsupported \"buffer\" type.'\n      );\n    }\n    const numComps = this.numComps;\n    if (numComps !== 1) {\n      throw new FormatError(\n        `Reading gray scale from a color image: ${numComps}`\n      );\n    }\n\n    const srcWidth = this.width;\n    const srcHeight = this.height;\n    const bpc = this.bpc;\n\n    // rows start at byte boundary\n    const rowBytes = (srcWidth * numComps * bpc + 7) >> 3;\n    const imgArray = await this.getImageBytes(srcHeight * rowBytes, {\n      internal: true,\n    });\n    const comps = this.getComponents(imgArray);\n\n    const resolvedDestWidth = destWidth ?? srcWidth;\n    const resolvedDestHeight = destHeight ?? srcHeight;\n    const needsResampling =","sourceCodeStart":1001,"sourceCodeEnd":1037,"githubUrl":"https://github.com/mozilla/pdf.js/blob/5903d58d58e4dd9ce6ffa3834aea8480f06b4ada/src/core/image.js#L1001-L1037","documentation":"Thrown by PDFImage.fillGrayBuffer when the image has more than one color component (numComps !== 1). fillGrayBuffer is meant to extract a grayscale channel; calling it on an RGB/CMYK image is a logic error because there is no single gray channel to copy.","triggerScenarios":"fillGrayBuffer is invoked on a multi-component image, typically as a sub-step of compositing a single-channel mask or smask whose underlying image was mistakenly treated as gray. Reached during alpha/mask fill operations.","commonSituations":"Usually an internal dispatch bug (a mask image resolved to a multi-component PDFImage) rather than a user config issue; can be triggered by PDFs whose SMask/Mask references point at a color image instead of a gray one.","solutions":["Report the PDF to pdf.js maintainers; this is normally an internal dispatch mismatch.","Ensure the source PDF's /SMask and /Mask reference single-channel (gray) images as the spec requires.","Catch FormatError and fall back to full RGBA compositing without the gray fast-path.","Regenerate the PDF so soft masks use 1-component images."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before calling fillGrayBuffer, ensure the image is single-channel:\nfunction canFillGray(image) {\n  return image?.numComps === 1;\n}","typeGuard":"function isSingleComponentImage(image) {\n  return image != null && image.numComps === 1;\n}","tryCatchPattern":"try { await page.render({ canvasContext }).promise; }\ncatch (err) {\n  if (/Reading gray scale from a color image/.test(err?.message || '')) {\n    console.warn('Gray fill requested on multi-component image; skipping.');\n  } else throw err;\n}","preventionTips":["Author /SMask and /Mask as single-channel images per the PDF spec.","Do not call fillGrayBuffer on RGB/CMYK images if you use pdf.js internals directly.","Report dispatcher bugs upstream with the reproducing PDF."],"tags":["pdf","image","grayscale","mask","internal"],"backgroundTag":null,"analyzedSha":"5903d58d58e4dd9ce6ffa3834aea8480f06b4ada","analyzedAt":"2026-08-13T02:28:27.364Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}