diff --git a/src/core/image.js b/src/core/image.js index 49c9edbcdfb47..5d2f1dd54e9d4 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -768,7 +768,7 @@ class PDFImage { return imgData; } - if (!forceRGBA) { + if (!forceRGBA && !this.smask && !this.mask) { // 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* @@ -788,8 +788,6 @@ class PDFImage { } if ( kind && - !this.smask && - !this.mask && drawWidth === originalWidth && drawHeight === originalHeight ) { @@ -831,12 +829,7 @@ class PDFImage { } return imgData; } - if ( - this.image instanceof JpegStream && - !this.smask && - !this.mask && - !this.needsDecode - ) { + if (this.image instanceof JpegStream && !this.needsDecode) { let isHandled = false; switch (this.colorSpace.name) { case "DeviceGray": diff --git a/src/core/jpeg_stream.js b/src/core/jpeg_stream.js index f1df733dcece5..8bb0c35109e79 100644 --- a/src/core/jpeg_stream.js +++ b/src/core/jpeg_stream.js @@ -64,30 +64,8 @@ class JpegStream extends DecodeStream { } get jpegOptions() { - const jpegOptions = { - decodeTransform: undefined, - colorTransform: undefined, - }; - - // Checking if values need to be transformed before conversion. - const decodeArr = this.dict.getArray("D", "Decode"); - if ((this.forceRGBA || this.forceRGB) && Array.isArray(decodeArr)) { - const bitsPerComponent = this.dict.get("BPC", "BitsPerComponent") || 8; - const decodeArrLength = decodeArr.length; - const transform = new Int32Array(decodeArrLength); - let transformNeeded = false; - const maxValue = (1 << bitsPerComponent) - 1; - for (let i = 0; i < decodeArrLength; i += 2) { - transform[i] = ((decodeArr[i + 1] - decodeArr[i]) * 256) | 0; - transform[i + 1] = (decodeArr[i] * maxValue) | 0; - if (transform[i] !== 256 || transform[i + 1] !== 0) { - transformNeeded = true; - } - } - if (transformNeeded) { - jpegOptions.decodeTransform = transform; - } - } + const jpegOptions = { colorTransform: undefined }; + // Fetching the 'ColorTransform' entry, if it exists. if (this.params instanceof Dict) { const colorTransform = this.params.get("ColorTransform"); @@ -146,11 +124,6 @@ class JpegStream extends DecodeStream { return null; } const jpegOptions = this.jpegOptions; - if (jpegOptions.decodeTransform) { - // TODO: We could decode the image thanks to ImageDecoder and then - // get the pixels with copyTo and apply the decodeTransform. - return null; - } let decoder; try { // TODO: If the stream is Flate & DCT we could try to just pipe the diff --git a/src/core/jpg.js b/src/core/jpg.js index 9c624b6e2db84..294e60e9426a0 100644 --- a/src/core/jpg.js +++ b/src/core/jpg.js @@ -804,9 +804,13 @@ function skipData(data, view, offset) { } class JpegImage { - constructor({ decodeTransform = null, colorTransform = -1 } = {}) { - this._decodeTransform = decodeTransform; - this._colorTransform = colorTransform; + constructor(options) { + this._colorTransform = options?.colorTransform ?? -1; + + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("IMAGE_DECODERS")) { + this._decodeTransform = options?.decodeTransform || null; + this._isSourcePDF = false; + } } static canUseImageDecoder(data, colorTransform = -1) { @@ -1210,7 +1214,7 @@ class JpegImage { return undefined; } - #getLinearizedBlockData(width, height, isSourcePDF) { + #getLinearizedBlockData(width, height) { const scaleX = this.width / width, scaleY = this.height / height; @@ -1253,35 +1257,32 @@ class JpegImage { } } - // decodeTransform contains pairs of multiplier (-256..256) and additive - let transform = this._decodeTransform; - - // In PDF files, JPEG images with CMYK colour spaces are usually inverted - // (this can be observed by extracting the raw image data). - // Since the conversion algorithms (see below) were written primarily for - // the PDF use-cases, attempting to use `JpegImage` to parse standalone - // JPEG (CMYK) images may thus result in inverted images (see issue 9513). - // - // Unfortunately it's not (always) possible to tell, from the image data - // alone, if it needs to be inverted. Thus in an attempt to provide better - // out-of-the-box behaviour when `JpegImage` is used standalone, default to - // inverting JPEG (CMYK) images if and only if the image data does *not* - // come from a PDF file and no `decodeTransform` was passed by the user. - if ( - typeof PDFJSDev !== "undefined" && - PDFJSDev.test("IMAGE_DECODERS") && - !isSourcePDF && - numComponents === 4 - ) { - transform ||= new Int32Array([ - -256, 255, -256, 255, -256, 255, -256, 255, - ]); - } + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("IMAGE_DECODERS")) { + // decodeTransform contains pairs of multiplier (-256..256) and additive + let transform = this._decodeTransform; + + // In PDF files, JPEG images with CMYK colour spaces are usually inverted + // (this can be observed by extracting the raw image data). + // Since the conversion algorithms (see below) were written primarily for + // the PDF use-cases, attempting to use `JpegImage` to parse standalone + // JPEG (CMYK) images may thus result in inverted images (see issue 9513). + // + // Unfortunately it's not (always) possible to tell, from the image data + // alone, if it needs to be inverted. Thus in an attempt to provide better + // out-of-the-box behaviour when `JpegImage` is used standalone, default + // to inverting JPEG (CMYK) images if and only if the image data does + // *not* come from a PDF file and no `decodeTransform` was provided. + if (!this._isSourcePDF && numComponents === 4) { + transform ||= new Int32Array([ + -256, 255, -256, 255, -256, 255, -256, 255, + ]); + } - if (transform) { - for (i = 0; i < dataLength;) { - for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) { - data[i] = ((data[i] * transform[k]) >> 8) + transform[k + 1]; + if (transform) { + for (i = 0; i < dataLength;) { + for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) { + data[i] = ((data[i] * transform[k]) >> 8) + transform[k + 1]; + } } } } @@ -1388,19 +1389,15 @@ class JpegImage { return data; } - getData({ - width, - height, - forceRGBA = false, - forceRGB = false, - isSourcePDF = typeof PDFJSDev === "undefined" || - !PDFJSDev.test("IMAGE_DECODERS"), - }) { + getData({ width, height, forceRGBA = false, forceRGB = false }) { if (this.numComponents > 4) { throw new JpegError("Unsupported color mode"); } + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("IMAGE_DECODERS")) { + this._isSourcePDF = arguments[0]?.isSourcePDF === true; + } // Type of data: Uint8ClampedArray(width * height * numComponents) - const data = this.#getLinearizedBlockData(width, height, isSourcePDF); + const data = this.#getLinearizedBlockData(width, height); if (this.numComponents === 1 && (forceRGBA || forceRGB)) { const len = data.length * (forceRGBA ? 4 : 3);