async createImageData()

in src/core/image.js [698:951]


  async createImageData(forceRGBA = false, isOffscreenCanvasSupported = false) {
    const drawWidth = this.drawWidth;
    const drawHeight = this.drawHeight;
    const imgData = {
      width: drawWidth,
      height: drawHeight,
      interpolate: this.interpolate,
      kind: 0,
      data: null,
      // Other fields are filled in below.
    };

    const numComps = this.numComps;
    const originalWidth = this.width;
    const originalHeight = this.height;
    const bpc = this.bpc;

    // Rows start at byte boundary.
    const rowBytes = (originalWidth * numComps * bpc + 7) >> 3;
    const mustBeResized =
      isOffscreenCanvasSupported &&
      ImageResizer.needsToBeResized(drawWidth, drawHeight);

    if (!this.smask && !this.mask && this.colorSpace.name === "DeviceRGBA") {
      imgData.kind = ImageKind.RGBA_32BPP;
      const imgArray = (imgData.data = await this.getImageBytes(
        originalHeight * originalWidth * 4,
        {}
      ));

      if (isOffscreenCanvasSupported) {
        if (!mustBeResized) {
          return this.createBitmap(
            ImageKind.RGBA_32BPP,
            drawWidth,
            drawHeight,
            imgArray
          );
        }
        return ImageResizer.createImage(imgData, false);
      }

      return imgData;
    }

    if (!forceRGBA) {
      // If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image
      // without any complications, we pass a same-sized copy to the main
      // thread rather than expanding by 32x to RGBA form. This saves *lots*
      // of memory for many scanned documents. It's also much faster.
      //
      // Similarly, if it is a 24-bit-per pixel RGB image without any
      // complications, we avoid expanding by 1.333x to RGBA form.
      let kind;
      if (this.colorSpace.name === "DeviceGray" && bpc === 1) {
        kind = ImageKind.GRAYSCALE_1BPP;
      } else if (
        this.colorSpace.name === "DeviceRGB" &&
        bpc === 8 &&
        !this.needsDecode
      ) {
        kind = ImageKind.RGB_24BPP;
      }
      if (
        kind &&
        !this.smask &&
        !this.mask &&
        drawWidth === originalWidth &&
        drawHeight === originalHeight
      ) {
        const image = await this.#getImage(originalWidth, originalHeight);
        if (image) {
          return image;
        }
        const data = await this.getImageBytes(originalHeight * rowBytes, {});
        if (isOffscreenCanvasSupported) {
          if (mustBeResized) {
            return ImageResizer.createImage(
              {
                data,
                kind,
                width: drawWidth,
                height: drawHeight,
                interpolate: this.interpolate,
              },
              this.needsDecode
            );
          }
          return this.createBitmap(kind, originalWidth, originalHeight, data);
        }
        imgData.kind = kind;
        imgData.data = data;

        if (this.needsDecode) {
          // Invert the buffer (which must be grayscale if we reached here).
          assert(
            kind === ImageKind.GRAYSCALE_1BPP,
            "PDFImage.createImageData: The image must be grayscale."
          );
          const buffer = imgData.data;
          for (let i = 0, ii = buffer.length; i < ii; i++) {
            buffer[i] ^= 0xff;
          }
        }
        return imgData;
      }
      if (
        this.image instanceof JpegStream &&
        !this.smask &&
        !this.mask &&
        !this.needsDecode
      ) {
        let imageLength = originalHeight * rowBytes;
        if (isOffscreenCanvasSupported && !mustBeResized) {
          let isHandled = false;
          switch (this.colorSpace.name) {
            case "DeviceGray":
              // Avoid truncating the image, since `JpegImage.getData`
              // will expand the image data when `forceRGB === true`.
              imageLength *= 4;
              isHandled = true;
              break;
            case "DeviceRGB":
              imageLength = (imageLength / 3) * 4;
              isHandled = true;
              break;
            case "DeviceCMYK":
              isHandled = true;
              break;
          }

          if (isHandled) {
            const image = await this.#getImage(drawWidth, drawHeight);
            if (image) {
              return image;
            }
            const rgba = await this.getImageBytes(imageLength, {
              drawWidth,
              drawHeight,
              forceRGBA: true,
            });
            return this.createBitmap(
              ImageKind.RGBA_32BPP,
              drawWidth,
              drawHeight,
              rgba
            );
          }
        } else {
          switch (this.colorSpace.name) {
            case "DeviceGray":
              imageLength *= 3;
            /* falls through */
            case "DeviceRGB":
            case "DeviceCMYK":
              imgData.kind = ImageKind.RGB_24BPP;
              imgData.data = await this.getImageBytes(imageLength, {
                drawWidth,
                drawHeight,
                forceRGB: true,
              });
              if (mustBeResized) {
                // The image is too big so we resize it.
                return ImageResizer.createImage(imgData);
              }
              return imgData;
          }
        }
      }
    }

    const imgArray = await this.getImageBytes(originalHeight * rowBytes, {
      internal: true,
    });
    // imgArray can be incomplete (e.g. after CCITT fax encoding).
    const actualHeight =
      0 | (((imgArray.length / rowBytes) * drawHeight) / originalHeight);

    const comps = this.getComponents(imgArray);

    // If opacity data is present, use RGBA_32BPP form. Otherwise, use the
    // more compact RGB_24BPP form if allowable.
    let alpha01, maybeUndoPreblend;

    let canvas, ctx, canvasImgData, data;
    if (isOffscreenCanvasSupported && !mustBeResized) {
      canvas = new OffscreenCanvas(drawWidth, drawHeight);
      ctx = canvas.getContext("2d");
      canvasImgData = ctx.createImageData(drawWidth, drawHeight);
      data = canvasImgData.data;
    }

    imgData.kind = ImageKind.RGBA_32BPP;

    if (!forceRGBA && !this.smask && !this.mask) {
      if (!isOffscreenCanvasSupported || mustBeResized) {
        imgData.kind = ImageKind.RGB_24BPP;
        data = new Uint8ClampedArray(drawWidth * drawHeight * 3);
        alpha01 = 0;
      } else {
        const arr = new Uint32Array(data.buffer);
        arr.fill(FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff);
        alpha01 = 1;
      }
      maybeUndoPreblend = false;
    } else {
      if (!isOffscreenCanvasSupported || mustBeResized) {
        data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
      }

      alpha01 = 1;
      maybeUndoPreblend = true;

      // Color key masking (opacity) must be performed before decoding.
      await this.fillOpacity(data, drawWidth, drawHeight, actualHeight, comps);
    }

    if (this.needsDecode) {
      this.decodeBuffer(comps);
    }
    this.colorSpace.fillRgb(
      data,
      originalWidth,
      originalHeight,
      drawWidth,
      drawHeight,
      actualHeight,
      bpc,
      comps,
      alpha01
    );
    if (maybeUndoPreblend) {
      this.undoPreblend(data, drawWidth, actualHeight);
    }

    if (isOffscreenCanvasSupported && !mustBeResized) {
      ctx.putImageData(canvasImgData, 0, 0);
      const bitmap = canvas.transferToImageBitmap();

      return {
        data: null,
        width: drawWidth,
        height: drawHeight,
        bitmap,
        interpolate: this.interpolate,
      };
    }

    imgData.data = data;
    if (mustBeResized) {
      return ImageResizer.createImage(imgData);
    }
    return imgData;
  }