Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions src/core/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -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*
Expand All @@ -788,8 +788,6 @@ class PDFImage {
}
if (
kind &&
!this.smask &&
!this.mask &&
drawWidth === originalWidth &&
drawHeight === originalHeight
) {
Expand Down Expand Up @@ -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":
Expand Down
31 changes: 2 additions & 29 deletions src/core/jpeg_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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
Expand Down
79 changes: 38 additions & 41 deletions src/core/jpg.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -1210,7 +1214,7 @@ class JpegImage {
return undefined;
}

#getLinearizedBlockData(width, height, isSourcePDF) {
#getLinearizedBlockData(width, height) {
const scaleX = this.width / width,
scaleY = this.height / height;

Expand Down Expand Up @@ -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];
}
}
}
}
Expand Down Expand Up @@ -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);
Expand Down
Loading