[api-minor] Generate images in the worker instead of the main thread.
We introduced the use of OffscreenCanvas in #14754 and this patch aims to use them for all kind of images. It'll slightly improve performances (and maybe slightly decrease memory use). Since an image can be rendered in using some transfer maps but because of OffscreenCanvas we don't have the underlying pixels array the transfer maps stuff is re-implemented in using the SVG filter feComponentTransfer.
This commit is contained in:
parent
9640add1f7
commit
fd03cd5493
@ -425,6 +425,8 @@ class Page {
|
|||||||
this.resources,
|
this.resources,
|
||||||
this.nonBlendModesSet
|
this.nonBlendModesSet
|
||||||
),
|
),
|
||||||
|
isOffscreenCanvasSupported:
|
||||||
|
this.evaluatorOptions.isOffscreenCanvasSupported,
|
||||||
pageIndex: this.pageIndex,
|
pageIndex: this.pageIndex,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
});
|
});
|
||||||
|
@ -716,7 +716,12 @@ class PartialEvaluator {
|
|||||||
});
|
});
|
||||||
// We force the use of RGBA_32BPP images here, because we can't handle
|
// We force the use of RGBA_32BPP images here, because we can't handle
|
||||||
// any other kind.
|
// any other kind.
|
||||||
imgData = imageObj.createImageData(/* forceRGBA = */ true);
|
imgData = imageObj.createImageData(
|
||||||
|
/* forceRGBA = */ true,
|
||||||
|
/* isOffscreenCanvasSupported = */ false
|
||||||
|
);
|
||||||
|
operatorList.isOffscreenCanvasSupported =
|
||||||
|
this.options.isOffscreenCanvasSupported;
|
||||||
operatorList.addImageOps(
|
operatorList.addImageOps(
|
||||||
OPS.paintInlineImageXObject,
|
OPS.paintInlineImageXObject,
|
||||||
[imgData],
|
[imgData],
|
||||||
@ -756,11 +761,22 @@ class PartialEvaluator {
|
|||||||
localColorSpaceCache,
|
localColorSpaceCache,
|
||||||
})
|
})
|
||||||
.then(imageObj => {
|
.then(imageObj => {
|
||||||
imgData = imageObj.createImageData(/* forceRGBA = */ false);
|
imgData = imageObj.createImageData(
|
||||||
|
/* forceRGBA = */ false,
|
||||||
|
/* isOffscreenCanvasSupported = */ this.options
|
||||||
|
.isOffscreenCanvasSupported
|
||||||
|
);
|
||||||
|
|
||||||
if (cacheKey && imageRef && cacheGlobally) {
|
if (cacheKey && imageRef && cacheGlobally) {
|
||||||
this.globalImageCache.addByteSize(imageRef, imgData.data.length);
|
let length = 0;
|
||||||
|
if (imgData.bitmap) {
|
||||||
|
length = imgData.width * imgData.height * 4;
|
||||||
|
} else {
|
||||||
|
length = imgData.data.length;
|
||||||
|
}
|
||||||
|
this.globalImageCache.addByteSize(imageRef, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._sendImgData(objId, imgData, cacheGlobally);
|
return this._sendImgData(objId, imgData, cacheGlobally);
|
||||||
})
|
})
|
||||||
.catch(reason => {
|
.catch(reason => {
|
||||||
|
@ -13,8 +13,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert, FormatError, ImageKind, info, warn } from "../shared/util.js";
|
import {
|
||||||
import { applyMaskImageData } from "../shared/image_utils.js";
|
assert,
|
||||||
|
FeatureTest,
|
||||||
|
FormatError,
|
||||||
|
ImageKind,
|
||||||
|
info,
|
||||||
|
warn,
|
||||||
|
} from "../shared/util.js";
|
||||||
|
import {
|
||||||
|
convertBlackAndWhiteToRGBA,
|
||||||
|
convertToRGBA,
|
||||||
|
} from "../shared/image_utils.js";
|
||||||
import { BaseStream } from "./base_stream.js";
|
import { BaseStream } from "./base_stream.js";
|
||||||
import { ColorSpace } from "./colorspace.js";
|
import { ColorSpace } from "./colorspace.js";
|
||||||
import { DecodeStream } from "./decode_stream.js";
|
import { DecodeStream } from "./decode_stream.js";
|
||||||
@ -364,11 +374,12 @@ class PDFImage {
|
|||||||
const canvas = new OffscreenCanvas(width, height);
|
const canvas = new OffscreenCanvas(width, height);
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const imgData = ctx.createImageData(width, height);
|
const imgData = ctx.createImageData(width, height);
|
||||||
applyMaskImageData({
|
convertBlackAndWhiteToRGBA({
|
||||||
src: imgArray,
|
src: imgArray,
|
||||||
dest: imgData.data,
|
dest: imgData.data,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
nonBlackColor: 0,
|
||||||
inverseDecode,
|
inverseDecode,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -641,7 +652,7 @@ class PDFImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createImageData(forceRGBA = false) {
|
createImageData(forceRGBA = false, isOffscreenCanvasSupported = false) {
|
||||||
const drawWidth = this.drawWidth;
|
const drawWidth = this.drawWidth;
|
||||||
const drawHeight = this.drawHeight;
|
const drawHeight = this.drawHeight;
|
||||||
const imgData = {
|
const imgData = {
|
||||||
@ -686,8 +697,12 @@ class PDFImage {
|
|||||||
drawWidth === originalWidth &&
|
drawWidth === originalWidth &&
|
||||||
drawHeight === originalHeight
|
drawHeight === originalHeight
|
||||||
) {
|
) {
|
||||||
|
const data = this.getImageBytes(originalHeight * rowBytes, {});
|
||||||
|
if (isOffscreenCanvasSupported) {
|
||||||
|
return this.createBitmap(kind, originalWidth, originalHeight, data);
|
||||||
|
}
|
||||||
imgData.kind = kind;
|
imgData.kind = kind;
|
||||||
imgData.data = this.getImageBytes(originalHeight * rowBytes, {});
|
imgData.data = data;
|
||||||
|
|
||||||
if (this.needsDecode) {
|
if (this.needsDecode) {
|
||||||
// Invert the buffer (which must be grayscale if we reached here).
|
// Invert the buffer (which must be grayscale if we reached here).
|
||||||
@ -704,21 +719,52 @@ class PDFImage {
|
|||||||
}
|
}
|
||||||
if (this.image instanceof JpegStream && !this.smask && !this.mask) {
|
if (this.image instanceof JpegStream && !this.smask && !this.mask) {
|
||||||
let imageLength = originalHeight * rowBytes;
|
let imageLength = originalHeight * rowBytes;
|
||||||
switch (this.colorSpace.name) {
|
if (isOffscreenCanvasSupported) {
|
||||||
case "DeviceGray":
|
let isHandled = false;
|
||||||
// Avoid truncating the image, since `JpegImage.getData`
|
switch (this.colorSpace.name) {
|
||||||
// will expand the image data when `forceRGB === true`.
|
case "DeviceGray":
|
||||||
imageLength *= 3;
|
// Avoid truncating the image, since `JpegImage.getData`
|
||||||
/* falls through */
|
// will expand the image data when `forceRGB === true`.
|
||||||
case "DeviceRGB":
|
imageLength *= 4;
|
||||||
case "DeviceCMYK":
|
isHandled = true;
|
||||||
imgData.kind = ImageKind.RGB_24BPP;
|
break;
|
||||||
imgData.data = this.getImageBytes(imageLength, {
|
case "DeviceRGB":
|
||||||
|
imageLength = (imageLength / 3) * 4;
|
||||||
|
isHandled = true;
|
||||||
|
break;
|
||||||
|
case "DeviceCMYK":
|
||||||
|
isHandled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHandled) {
|
||||||
|
const rgba = this.getImageBytes(imageLength, {
|
||||||
drawWidth,
|
drawWidth,
|
||||||
drawHeight,
|
drawHeight,
|
||||||
forceRGB: true,
|
forceRGBA: true,
|
||||||
});
|
});
|
||||||
return imgData;
|
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 = this.getImageBytes(imageLength, {
|
||||||
|
drawWidth,
|
||||||
|
drawHeight,
|
||||||
|
forceRGB: true,
|
||||||
|
});
|
||||||
|
return imgData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -735,32 +781,45 @@ class PDFImage {
|
|||||||
// If opacity data is present, use RGBA_32BPP form. Otherwise, use the
|
// If opacity data is present, use RGBA_32BPP form. Otherwise, use the
|
||||||
// more compact RGB_24BPP form if allowable.
|
// more compact RGB_24BPP form if allowable.
|
||||||
let alpha01, maybeUndoPreblend;
|
let alpha01, maybeUndoPreblend;
|
||||||
|
|
||||||
|
let canvas, ctx, canvasImgData, data;
|
||||||
|
if (isOffscreenCanvasSupported) {
|
||||||
|
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 (!forceRGBA && !this.smask && !this.mask) {
|
||||||
imgData.kind = ImageKind.RGB_24BPP;
|
if (!isOffscreenCanvasSupported) {
|
||||||
imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 3);
|
imgData.kind = ImageKind.RGB_24BPP;
|
||||||
alpha01 = 0;
|
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;
|
maybeUndoPreblend = false;
|
||||||
} else {
|
} else {
|
||||||
imgData.kind = ImageKind.RGBA_32BPP;
|
if (!isOffscreenCanvasSupported) {
|
||||||
imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
|
data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
|
||||||
|
}
|
||||||
|
|
||||||
alpha01 = 1;
|
alpha01 = 1;
|
||||||
maybeUndoPreblend = true;
|
maybeUndoPreblend = true;
|
||||||
|
|
||||||
// Color key masking (opacity) must be performed before decoding.
|
// Color key masking (opacity) must be performed before decoding.
|
||||||
this.fillOpacity(
|
this.fillOpacity(data, drawWidth, drawHeight, actualHeight, comps);
|
||||||
imgData.data,
|
|
||||||
drawWidth,
|
|
||||||
drawHeight,
|
|
||||||
actualHeight,
|
|
||||||
comps
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.needsDecode) {
|
if (this.needsDecode) {
|
||||||
this.decodeBuffer(comps);
|
this.decodeBuffer(comps);
|
||||||
}
|
}
|
||||||
this.colorSpace.fillRgb(
|
this.colorSpace.fillRgb(
|
||||||
imgData.data,
|
data,
|
||||||
originalWidth,
|
originalWidth,
|
||||||
originalHeight,
|
originalHeight,
|
||||||
drawWidth,
|
drawWidth,
|
||||||
@ -771,9 +830,23 @@ class PDFImage {
|
|||||||
alpha01
|
alpha01
|
||||||
);
|
);
|
||||||
if (maybeUndoPreblend) {
|
if (maybeUndoPreblend) {
|
||||||
this.undoPreblend(imgData.data, drawWidth, actualHeight);
|
this.undoPreblend(data, drawWidth, actualHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isOffscreenCanvasSupported) {
|
||||||
|
ctx.putImageData(canvasImgData, 0, 0);
|
||||||
|
const bitmap = canvas.transferToImageBitmap();
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
width: drawWidth,
|
||||||
|
height: drawHeight,
|
||||||
|
bitmap,
|
||||||
|
interpolate: this.interpolate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
imgData.data = data;
|
||||||
return imgData;
|
return imgData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,13 +906,49 @@ class PDFImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createBitmap(kind, width, height, src) {
|
||||||
|
const canvas = new OffscreenCanvas(width, height);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
let imgData;
|
||||||
|
if (kind === ImageKind.RGBA_32BPP) {
|
||||||
|
imgData = new ImageData(src, width, height);
|
||||||
|
} else {
|
||||||
|
imgData = ctx.createImageData(width, height);
|
||||||
|
convertToRGBA({
|
||||||
|
kind,
|
||||||
|
src,
|
||||||
|
dest: new Uint32Array(imgData.data.buffer),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
inverseDecode: this.needsDecode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ctx.putImageData(imgData, 0, 0);
|
||||||
|
const bitmap = canvas.transferToImageBitmap();
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bitmap,
|
||||||
|
interpolate: this.interpolate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getImageBytes(
|
getImageBytes(
|
||||||
length,
|
length,
|
||||||
{ drawWidth, drawHeight, forceRGB = false, internal = false }
|
{
|
||||||
|
drawWidth,
|
||||||
|
drawHeight,
|
||||||
|
forceRGBA = false,
|
||||||
|
forceRGB = false,
|
||||||
|
internal = false,
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
this.image.reset();
|
this.image.reset();
|
||||||
this.image.drawWidth = drawWidth || this.width;
|
this.image.drawWidth = drawWidth || this.width;
|
||||||
this.image.drawHeight = drawHeight || this.height;
|
this.image.drawHeight = drawHeight || this.height;
|
||||||
|
this.image.forceRGBA = !!forceRGBA;
|
||||||
this.image.forceRGB = !!forceRGB;
|
this.image.forceRGB = !!forceRGB;
|
||||||
const imageBytes = this.image.getBytes(length);
|
const imageBytes = this.image.getBytes(length);
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ class JpegStream extends DecodeStream {
|
|||||||
|
|
||||||
// Checking if values need to be transformed before conversion.
|
// Checking if values need to be transformed before conversion.
|
||||||
const decodeArr = this.dict.getArray("D", "Decode");
|
const decodeArr = this.dict.getArray("D", "Decode");
|
||||||
if (this.forceRGB && Array.isArray(decodeArr)) {
|
if ((this.forceRGBA || this.forceRGB) && Array.isArray(decodeArr)) {
|
||||||
const bitsPerComponent = this.dict.get("BPC", "BitsPerComponent") || 8;
|
const bitsPerComponent = this.dict.get("BPC", "BitsPerComponent") || 8;
|
||||||
const decodeArrLength = decodeArr.length;
|
const decodeArrLength = decodeArr.length;
|
||||||
const transform = new Int32Array(decodeArrLength);
|
const transform = new Int32Array(decodeArrLength);
|
||||||
@ -93,6 +93,7 @@ class JpegStream extends DecodeStream {
|
|||||||
const data = jpegImage.getData({
|
const data = jpegImage.getData({
|
||||||
width: this.drawWidth,
|
width: this.drawWidth,
|
||||||
height: this.drawHeight,
|
height: this.drawHeight,
|
||||||
|
forceRGBA: this.forceRGBA,
|
||||||
forceRGB: this.forceRGB,
|
forceRGB: this.forceRGB,
|
||||||
isSourcePDF: true,
|
isSourcePDF: true,
|
||||||
});
|
});
|
||||||
|
186
src/core/jpg.js
186
src/core/jpg.js
@ -14,6 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert, BaseException, warn } from "../shared/util.js";
|
import { assert, BaseException, warn } from "../shared/util.js";
|
||||||
|
import { grayToRGBA } from "../shared/image_utils.js";
|
||||||
import { readUint16 } from "./core_utils.js";
|
import { readUint16 } from "./core_utils.js";
|
||||||
|
|
||||||
class JpegError extends BaseException {
|
class JpegError extends BaseException {
|
||||||
@ -1217,6 +1218,19 @@ class JpegImage {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_convertYccToRgba(data, out) {
|
||||||
|
for (let i = 0, j = 0, length = data.length; i < length; i += 3, j += 4) {
|
||||||
|
const Y = data[i];
|
||||||
|
const Cb = data[i + 1];
|
||||||
|
const Cr = data[i + 2];
|
||||||
|
out[j] = Y - 179.456 + 1.402 * Cr;
|
||||||
|
out[j + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr;
|
||||||
|
out[j + 2] = Y - 226.816 + 1.772 * Cb;
|
||||||
|
out[j + 3] = 255;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
_convertYcckToRgb(data) {
|
_convertYcckToRgb(data) {
|
||||||
let Y, Cb, Cr, k;
|
let Y, Cb, Cr, k;
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
@ -1287,6 +1301,74 @@ class JpegImage {
|
|||||||
return data.subarray(0, offset);
|
return data.subarray(0, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_convertYcckToRgba(data) {
|
||||||
|
for (let i = 0, length = data.length; i < length; i += 4) {
|
||||||
|
const Y = data[i];
|
||||||
|
const Cb = data[i + 1];
|
||||||
|
const Cr = data[i + 2];
|
||||||
|
const k = data[i + 3];
|
||||||
|
|
||||||
|
data[i] =
|
||||||
|
-122.67195406894 +
|
||||||
|
Cb *
|
||||||
|
(-6.60635669420364e-5 * Cb +
|
||||||
|
0.000437130475926232 * Cr -
|
||||||
|
5.4080610064599e-5 * Y +
|
||||||
|
0.00048449797120281 * k -
|
||||||
|
0.154362151871126) +
|
||||||
|
Cr *
|
||||||
|
(-0.000957964378445773 * Cr +
|
||||||
|
0.000817076911346625 * Y -
|
||||||
|
0.00477271405408747 * k +
|
||||||
|
1.53380253221734) +
|
||||||
|
Y *
|
||||||
|
(0.000961250184130688 * Y -
|
||||||
|
0.00266257332283933 * k +
|
||||||
|
0.48357088451265) +
|
||||||
|
k * (-0.000336197177618394 * k + 0.484791561490776);
|
||||||
|
|
||||||
|
data[i + 1] =
|
||||||
|
107.268039397724 +
|
||||||
|
Cb *
|
||||||
|
(2.19927104525741e-5 * Cb -
|
||||||
|
0.000640992018297945 * Cr +
|
||||||
|
0.000659397001245577 * Y +
|
||||||
|
0.000426105652938837 * k -
|
||||||
|
0.176491792462875) +
|
||||||
|
Cr *
|
||||||
|
(-0.000778269941513683 * Cr +
|
||||||
|
0.00130872261408275 * Y +
|
||||||
|
0.000770482631801132 * k -
|
||||||
|
0.151051492775562) +
|
||||||
|
Y *
|
||||||
|
(0.00126935368114843 * Y -
|
||||||
|
0.00265090189010898 * k +
|
||||||
|
0.25802910206845) +
|
||||||
|
k * (-0.000318913117588328 * k - 0.213742400323665);
|
||||||
|
|
||||||
|
data[i + 2] =
|
||||||
|
-20.810012546947 +
|
||||||
|
Cb *
|
||||||
|
(-0.000570115196973677 * Cb -
|
||||||
|
2.63409051004589e-5 * Cr +
|
||||||
|
0.0020741088115012 * Y -
|
||||||
|
0.00288260236853442 * k +
|
||||||
|
0.814272968359295) +
|
||||||
|
Cr *
|
||||||
|
(-1.53496057440975e-5 * Cr -
|
||||||
|
0.000132689043961446 * Y +
|
||||||
|
0.000560833691242812 * k -
|
||||||
|
0.195152027534049) +
|
||||||
|
Y *
|
||||||
|
(0.00174418132927582 * Y -
|
||||||
|
0.00255243321439347 * k +
|
||||||
|
0.116935020465145) +
|
||||||
|
k * (-0.000343531996510555 * k + 0.24165260232407);
|
||||||
|
data[i + 3] = 255;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
_convertYcckToCmyk(data) {
|
_convertYcckToCmyk(data) {
|
||||||
let Y, Cb, Cr;
|
let Y, Cb, Cr;
|
||||||
for (let i = 0, length = data.length; i < length; i += 4) {
|
for (let i = 0, length = data.length; i < length; i += 4) {
|
||||||
@ -1371,7 +1453,81 @@ class JpegImage {
|
|||||||
return data.subarray(0, offset);
|
return data.subarray(0, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
getData({ width, height, forceRGB = false, isSourcePDF = false }) {
|
_convertCmykToRgba(data) {
|
||||||
|
for (let i = 0, length = data.length; i < length; i += 4) {
|
||||||
|
const c = data[i];
|
||||||
|
const m = data[i + 1];
|
||||||
|
const y = data[i + 2];
|
||||||
|
const k = data[i + 3];
|
||||||
|
|
||||||
|
data[i] =
|
||||||
|
255 +
|
||||||
|
c *
|
||||||
|
(-0.00006747147073602441 * c +
|
||||||
|
0.0008379262121013727 * m +
|
||||||
|
0.0002894718188643294 * y +
|
||||||
|
0.003264231057537806 * k -
|
||||||
|
1.1185611867203937) +
|
||||||
|
m *
|
||||||
|
(0.000026374107616089405 * m -
|
||||||
|
0.00008626949158638572 * y -
|
||||||
|
0.0002748769067499491 * k -
|
||||||
|
0.02155688794978967) +
|
||||||
|
y *
|
||||||
|
(-0.00003878099212869363 * y -
|
||||||
|
0.0003267808279485286 * k +
|
||||||
|
0.0686742238595345) -
|
||||||
|
k * (0.0003361971776183937 * k + 0.7430659151342254);
|
||||||
|
|
||||||
|
data[i + 1] =
|
||||||
|
255 +
|
||||||
|
c *
|
||||||
|
(0.00013596372813588848 * c +
|
||||||
|
0.000924537132573585 * m +
|
||||||
|
0.00010567359618683593 * y +
|
||||||
|
0.0004791864687436512 * k -
|
||||||
|
0.3109689587515875) +
|
||||||
|
m *
|
||||||
|
(-0.00023545346108370344 * m +
|
||||||
|
0.0002702845253534714 * y +
|
||||||
|
0.0020200308977307156 * k -
|
||||||
|
0.7488052167015494) +
|
||||||
|
y *
|
||||||
|
(0.00006834815998235662 * y +
|
||||||
|
0.00015168452363460973 * k -
|
||||||
|
0.09751927774728933) -
|
||||||
|
k * (0.0003189131175883281 * k + 0.7364883807733168);
|
||||||
|
|
||||||
|
data[i + 2] =
|
||||||
|
255 +
|
||||||
|
c *
|
||||||
|
(0.000013598650411385307 * c +
|
||||||
|
0.00012423956175490851 * m +
|
||||||
|
0.0004751985097583589 * y -
|
||||||
|
0.0000036729317476630422 * k -
|
||||||
|
0.05562186980264034) +
|
||||||
|
m *
|
||||||
|
(0.00016141380598724676 * m +
|
||||||
|
0.0009692239130725186 * y +
|
||||||
|
0.0007782692450036253 * k -
|
||||||
|
0.44015232367526463) +
|
||||||
|
y *
|
||||||
|
(5.068882914068769e-7 * y +
|
||||||
|
0.0017778369011375071 * k -
|
||||||
|
0.7591454649749609) -
|
||||||
|
k * (0.0003435319965105553 * k + 0.7063770186160144);
|
||||||
|
data[i + 3] = 255;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getData({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
forceRGBA = false,
|
||||||
|
forceRGB = false,
|
||||||
|
isSourcePDF = false,
|
||||||
|
}) {
|
||||||
if (
|
if (
|
||||||
typeof PDFJSDev === "undefined" ||
|
typeof PDFJSDev === "undefined" ||
|
||||||
PDFJSDev.test("!PRODUCTION || TESTING")
|
PDFJSDev.test("!PRODUCTION || TESTING")
|
||||||
@ -1387,23 +1543,37 @@ class JpegImage {
|
|||||||
// Type of data: Uint8ClampedArray(width * height * numComponents)
|
// Type of data: Uint8ClampedArray(width * height * numComponents)
|
||||||
const data = this._getLinearizedBlockData(width, height, isSourcePDF);
|
const data = this._getLinearizedBlockData(width, height, isSourcePDF);
|
||||||
|
|
||||||
if (this.numComponents === 1 && forceRGB) {
|
if (this.numComponents === 1 && (forceRGBA || forceRGB)) {
|
||||||
const rgbData = new Uint8ClampedArray(data.length * 3);
|
const len = data.length * (forceRGBA ? 4 : 3);
|
||||||
|
const rgbaData = new Uint8ClampedArray(len);
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for (const grayColor of data) {
|
if (forceRGBA) {
|
||||||
rgbData[offset++] = grayColor;
|
grayToRGBA(data, new Uint32Array(rgbaData.buffer));
|
||||||
rgbData[offset++] = grayColor;
|
} else {
|
||||||
rgbData[offset++] = grayColor;
|
for (const grayColor of data) {
|
||||||
|
rgbaData[offset++] = grayColor;
|
||||||
|
rgbaData[offset++] = grayColor;
|
||||||
|
rgbaData[offset++] = grayColor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return rgbData;
|
return rgbaData;
|
||||||
} else if (this.numComponents === 3 && this._isColorConversionNeeded) {
|
} else if (this.numComponents === 3 && this._isColorConversionNeeded) {
|
||||||
|
if (forceRGBA) {
|
||||||
|
const rgbaData = new Uint8ClampedArray((data.length / 3) * 4);
|
||||||
|
return this._convertYccToRgba(data, rgbaData);
|
||||||
|
}
|
||||||
return this._convertYccToRgb(data);
|
return this._convertYccToRgb(data);
|
||||||
} else if (this.numComponents === 4) {
|
} else if (this.numComponents === 4) {
|
||||||
if (this._isColorConversionNeeded) {
|
if (this._isColorConversionNeeded) {
|
||||||
|
if (forceRGBA) {
|
||||||
|
return this._convertYcckToRgba(data);
|
||||||
|
}
|
||||||
if (forceRGB) {
|
if (forceRGB) {
|
||||||
return this._convertYcckToRgb(data);
|
return this._convertYcckToRgb(data);
|
||||||
}
|
}
|
||||||
return this._convertYcckToCmyk(data);
|
return this._convertYcckToCmyk(data);
|
||||||
|
} else if (forceRGBA) {
|
||||||
|
return this._convertCmykToRgba(data);
|
||||||
} else if (forceRGB) {
|
} else if (forceRGB) {
|
||||||
return this._convertCmykToRgb(data);
|
return this._convertCmykToRgb(data);
|
||||||
}
|
}
|
||||||
|
@ -136,17 +136,32 @@ addState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const img = {
|
||||||
|
width: imgWidth,
|
||||||
|
height: imgHeight,
|
||||||
|
};
|
||||||
|
if (context.isOffscreenCanvasSupported) {
|
||||||
|
const canvas = new OffscreenCanvas(imgWidth, imgHeight);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
ctx.putImageData(
|
||||||
|
new ImageData(
|
||||||
|
new Uint8ClampedArray(imgData.buffer),
|
||||||
|
imgWidth,
|
||||||
|
imgHeight
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
img.bitmap = canvas.transferToImageBitmap();
|
||||||
|
img.data = null;
|
||||||
|
} else {
|
||||||
|
img.kind = ImageKind.RGBA_32BPP;
|
||||||
|
img.data = imgData;
|
||||||
|
}
|
||||||
|
|
||||||
// Replace queue items.
|
// Replace queue items.
|
||||||
fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup);
|
fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup);
|
||||||
argsArray.splice(iFirstSave, count * 4, [
|
argsArray.splice(iFirstSave, count * 4, [img, map]);
|
||||||
{
|
|
||||||
width: imgWidth,
|
|
||||||
height: imgHeight,
|
|
||||||
kind: ImageKind.RGBA_32BPP,
|
|
||||||
data: imgData,
|
|
||||||
},
|
|
||||||
map,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return iFirstSave + 1;
|
return iFirstSave + 1;
|
||||||
}
|
}
|
||||||
@ -487,11 +502,17 @@ class QueueOptimizer extends NullOptimizer {
|
|||||||
iCurr: 0,
|
iCurr: 0,
|
||||||
fnArray: queue.fnArray,
|
fnArray: queue.fnArray,
|
||||||
argsArray: queue.argsArray,
|
argsArray: queue.argsArray,
|
||||||
|
isOffscreenCanvasSupported: false,
|
||||||
};
|
};
|
||||||
this.match = null;
|
this.match = null;
|
||||||
this.lastProcessed = 0;
|
this.lastProcessed = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line accessor-pairs
|
||||||
|
set isOffscreenCanvasSupported(value) {
|
||||||
|
this.context.isOffscreenCanvasSupported = value;
|
||||||
|
}
|
||||||
|
|
||||||
_optimize() {
|
_optimize() {
|
||||||
// Process new fnArray item(s) chunk.
|
// Process new fnArray item(s) chunk.
|
||||||
const fnArray = this.queue.fnArray;
|
const fnArray = this.queue.fnArray;
|
||||||
@ -589,6 +610,11 @@ class OperatorList {
|
|||||||
this._resolved = streamSink ? null : Promise.resolve();
|
this._resolved = streamSink ? null : Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line accessor-pairs
|
||||||
|
set isOffscreenCanvasSupported(value) {
|
||||||
|
this.optimizer.isOffscreenCanvasSupported = value;
|
||||||
|
}
|
||||||
|
|
||||||
get length() {
|
get length() {
|
||||||
return this.argsArray.length;
|
return this.argsArray.length;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ import {
|
|||||||
DOMCanvasFactory,
|
DOMCanvasFactory,
|
||||||
DOMCMapReaderFactory,
|
DOMCMapReaderFactory,
|
||||||
DOMStandardFontDataFactory,
|
DOMStandardFontDataFactory,
|
||||||
|
FilterFactory,
|
||||||
isDataScheme,
|
isDataScheme,
|
||||||
isValidFetchUrl,
|
isValidFetchUrl,
|
||||||
loadScript,
|
loadScript,
|
||||||
@ -232,6 +233,8 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) {
|
|||||||
* (see `web/debugger.js`). The default value is `false`.
|
* (see `web/debugger.js`). The default value is `false`.
|
||||||
* @property {Object} [canvasFactory] - The factory instance that will be used
|
* @property {Object} [canvasFactory] - The factory instance that will be used
|
||||||
* when creating canvases. The default value is {new DOMCanvasFactory()}.
|
* when creating canvases. The default value is {new DOMCanvasFactory()}.
|
||||||
|
* @property {Object} [filterFactory] - A factory instance that will be used
|
||||||
|
* to create SVG filters when rendering some images on the main canvas.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -341,6 +344,8 @@ function getDocument(src) {
|
|||||||
isValidFetchUrl(standardFontDataUrl, document.baseURI));
|
isValidFetchUrl(standardFontDataUrl, document.baseURI));
|
||||||
const canvasFactory =
|
const canvasFactory =
|
||||||
src.canvasFactory || new DefaultCanvasFactory({ ownerDocument });
|
src.canvasFactory || new DefaultCanvasFactory({ ownerDocument });
|
||||||
|
const filterFactory =
|
||||||
|
src.filterFactory || new FilterFactory({ ownerDocument });
|
||||||
|
|
||||||
// Parameters only intended for development/testing purposes.
|
// Parameters only intended for development/testing purposes.
|
||||||
const styleElement =
|
const styleElement =
|
||||||
@ -355,6 +360,7 @@ function getDocument(src) {
|
|||||||
// since the user may provide *custom* ones.
|
// since the user may provide *custom* ones.
|
||||||
const transportFactory = {
|
const transportFactory = {
|
||||||
canvasFactory,
|
canvasFactory,
|
||||||
|
filterFactory,
|
||||||
};
|
};
|
||||||
if (!useWorkerFetch) {
|
if (!useWorkerFetch) {
|
||||||
transportFactory.cMapReaderFactory = new CMapReaderFactory({
|
transportFactory.cMapReaderFactory = new CMapReaderFactory({
|
||||||
@ -1514,6 +1520,7 @@ class PDFPageProxy {
|
|||||||
operatorList: intentState.operatorList,
|
operatorList: intentState.operatorList,
|
||||||
pageIndex: this._pageIndex,
|
pageIndex: this._pageIndex,
|
||||||
canvasFactory: canvasFactory || this._transport.canvasFactory,
|
canvasFactory: canvasFactory || this._transport.canvasFactory,
|
||||||
|
filterFactory: this._transport.filterFactory,
|
||||||
useRequestAnimationFrame: !intentPrint,
|
useRequestAnimationFrame: !intentPrint,
|
||||||
pdfBug: this._pdfBug,
|
pdfBug: this._pdfBug,
|
||||||
pageColors,
|
pageColors,
|
||||||
@ -1526,19 +1533,25 @@ class PDFPageProxy {
|
|||||||
intentState.displayReadyCapability.promise,
|
intentState.displayReadyCapability.promise,
|
||||||
optionalContentConfigPromise,
|
optionalContentConfigPromise,
|
||||||
])
|
])
|
||||||
.then(([transparency, optionalContentConfig]) => {
|
.then(
|
||||||
if (this.pendingCleanup) {
|
([
|
||||||
complete();
|
{ transparency, isOffscreenCanvasSupported },
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._stats?.time("Rendering");
|
|
||||||
|
|
||||||
internalRenderTask.initializeGraphics({
|
|
||||||
transparency,
|
|
||||||
optionalContentConfig,
|
optionalContentConfig,
|
||||||
});
|
]) => {
|
||||||
internalRenderTask.operatorListChanged();
|
if (this.pendingCleanup) {
|
||||||
})
|
complete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._stats?.time("Rendering");
|
||||||
|
|
||||||
|
internalRenderTask.initializeGraphics({
|
||||||
|
transparency,
|
||||||
|
isOffscreenCanvasSupported,
|
||||||
|
optionalContentConfig,
|
||||||
|
});
|
||||||
|
internalRenderTask.operatorListChanged();
|
||||||
|
}
|
||||||
|
)
|
||||||
.catch(complete);
|
.catch(complete);
|
||||||
|
|
||||||
return renderTask;
|
return renderTask;
|
||||||
@ -1739,7 +1752,7 @@ class PDFPageProxy {
|
|||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_startRenderPage(transparency, cacheKey) {
|
_startRenderPage(transparency, isOffscreenCanvasSupported, cacheKey) {
|
||||||
const intentState = this._intentStates.get(cacheKey);
|
const intentState = this._intentStates.get(cacheKey);
|
||||||
if (!intentState) {
|
if (!intentState) {
|
||||||
return; // Rendering was cancelled.
|
return; // Rendering was cancelled.
|
||||||
@ -1748,7 +1761,10 @@ class PDFPageProxy {
|
|||||||
|
|
||||||
// TODO Refactor RenderPageRequest to separate rendering
|
// TODO Refactor RenderPageRequest to separate rendering
|
||||||
// and operator list logic
|
// and operator list logic
|
||||||
intentState.displayReadyCapability?.resolve(transparency);
|
intentState.displayReadyCapability?.resolve({
|
||||||
|
transparency,
|
||||||
|
isOffscreenCanvasSupported,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2357,6 +2373,7 @@ class WorkerTransport {
|
|||||||
this._params = params;
|
this._params = params;
|
||||||
|
|
||||||
this.canvasFactory = factory.canvasFactory;
|
this.canvasFactory = factory.canvasFactory;
|
||||||
|
this.filterFactory = factory.filterFactory;
|
||||||
this.cMapReaderFactory = factory.cMapReaderFactory;
|
this.cMapReaderFactory = factory.cMapReaderFactory;
|
||||||
this.standardFontDataFactory = factory.standardFontDataFactory;
|
this.standardFontDataFactory = factory.standardFontDataFactory;
|
||||||
|
|
||||||
@ -2489,6 +2506,7 @@ class WorkerTransport {
|
|||||||
this.commonObjs.clear();
|
this.commonObjs.clear();
|
||||||
this.fontLoader.clear();
|
this.fontLoader.clear();
|
||||||
this.#methodPromises.clear();
|
this.#methodPromises.clear();
|
||||||
|
this.filterFactory.destroy();
|
||||||
|
|
||||||
if (this._networkStream) {
|
if (this._networkStream) {
|
||||||
this._networkStream.cancelAllRequests(
|
this._networkStream.cancelAllRequests(
|
||||||
@ -2709,7 +2727,11 @@ class WorkerTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const page = this.#pageCache.get(data.pageIndex);
|
const page = this.#pageCache.get(data.pageIndex);
|
||||||
page._startRenderPage(data.transparency, data.cacheKey);
|
page._startRenderPage(
|
||||||
|
data.transparency,
|
||||||
|
data.isOffscreenCanvasSupported,
|
||||||
|
data.cacheKey
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
messageHandler.on("commonobj", ([id, type, exportedData]) => {
|
messageHandler.on("commonobj", ([id, type, exportedData]) => {
|
||||||
@ -3079,6 +3101,7 @@ class WorkerTransport {
|
|||||||
this.fontLoader.clear();
|
this.fontLoader.clear();
|
||||||
}
|
}
|
||||||
this.#methodPromises.clear();
|
this.#methodPromises.clear();
|
||||||
|
this.filterFactory.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
get loadingParams() {
|
get loadingParams() {
|
||||||
@ -3246,6 +3269,7 @@ class InternalRenderTask {
|
|||||||
operatorList,
|
operatorList,
|
||||||
pageIndex,
|
pageIndex,
|
||||||
canvasFactory,
|
canvasFactory,
|
||||||
|
filterFactory,
|
||||||
useRequestAnimationFrame = false,
|
useRequestAnimationFrame = false,
|
||||||
pdfBug = false,
|
pdfBug = false,
|
||||||
pageColors = null,
|
pageColors = null,
|
||||||
@ -3259,6 +3283,7 @@ class InternalRenderTask {
|
|||||||
this.operatorList = operatorList;
|
this.operatorList = operatorList;
|
||||||
this._pageIndex = pageIndex;
|
this._pageIndex = pageIndex;
|
||||||
this.canvasFactory = canvasFactory;
|
this.canvasFactory = canvasFactory;
|
||||||
|
this.filterFactory = filterFactory;
|
||||||
this._pdfBug = pdfBug;
|
this._pdfBug = pdfBug;
|
||||||
this.pageColors = pageColors;
|
this.pageColors = pageColors;
|
||||||
|
|
||||||
@ -3285,7 +3310,11 @@ class InternalRenderTask {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeGraphics({ transparency = false, optionalContentConfig }) {
|
initializeGraphics({
|
||||||
|
transparency = false,
|
||||||
|
isOffscreenCanvasSupported = false,
|
||||||
|
optionalContentConfig,
|
||||||
|
}) {
|
||||||
if (this.cancelled) {
|
if (this.cancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3312,6 +3341,7 @@ class InternalRenderTask {
|
|||||||
this.commonObjs,
|
this.commonObjs,
|
||||||
this.objs,
|
this.objs,
|
||||||
this.canvasFactory,
|
this.canvasFactory,
|
||||||
|
isOffscreenCanvasSupported ? this.filterFactory : null,
|
||||||
{ optionalContentConfig },
|
{ optionalContentConfig },
|
||||||
this.annotationCanvasMap,
|
this.annotationCanvasMap,
|
||||||
this.pageColors
|
this.pageColors
|
||||||
|
@ -37,7 +37,7 @@ import {
|
|||||||
PathType,
|
PathType,
|
||||||
TilingPattern,
|
TilingPattern,
|
||||||
} from "./pattern_helper.js";
|
} from "./pattern_helper.js";
|
||||||
import { applyMaskImageData } from "../shared/image_utils.js";
|
import { convertBlackAndWhiteToRGBA } from "../shared/image_utils.js";
|
||||||
|
|
||||||
// <canvas> contexts store most of the state we need natively.
|
// <canvas> contexts store most of the state we need natively.
|
||||||
// However, PDF needs a bit more state, which we store here.
|
// However, PDF needs a bit more state, which we store here.
|
||||||
@ -812,12 +812,13 @@ function putBinaryImageMask(ctx, imgData) {
|
|||||||
// Expand the mask so it can be used by the canvas. Any required
|
// Expand the mask so it can be used by the canvas. Any required
|
||||||
// inversion has already been handled.
|
// inversion has already been handled.
|
||||||
|
|
||||||
({ srcPos } = applyMaskImageData({
|
({ srcPos } = convertBlackAndWhiteToRGBA({
|
||||||
src,
|
src,
|
||||||
srcPos,
|
srcPos,
|
||||||
dest,
|
dest,
|
||||||
width,
|
width,
|
||||||
height: thisChunkHeight,
|
height: thisChunkHeight,
|
||||||
|
nonBlackColor: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
|
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
|
||||||
@ -1015,6 +1016,7 @@ class CanvasGraphics {
|
|||||||
commonObjs,
|
commonObjs,
|
||||||
objs,
|
objs,
|
||||||
canvasFactory,
|
canvasFactory,
|
||||||
|
filterFactory,
|
||||||
{ optionalContentConfig, markedContentStack = null },
|
{ optionalContentConfig, markedContentStack = null },
|
||||||
annotationCanvasMap,
|
annotationCanvasMap,
|
||||||
pageColors
|
pageColors
|
||||||
@ -1032,6 +1034,7 @@ class CanvasGraphics {
|
|||||||
this.commonObjs = commonObjs;
|
this.commonObjs = commonObjs;
|
||||||
this.objs = objs;
|
this.objs = objs;
|
||||||
this.canvasFactory = canvasFactory;
|
this.canvasFactory = canvasFactory;
|
||||||
|
this.filterFactory = filterFactory;
|
||||||
this.groupStack = [];
|
this.groupStack = [];
|
||||||
this.processingType3 = null;
|
this.processingType3 = null;
|
||||||
// Patterns are painted relative to the initial page/form transform, see
|
// Patterns are painted relative to the initial page/form transform, see
|
||||||
@ -1573,7 +1576,10 @@ class CanvasGraphics {
|
|||||||
this.checkSMaskState();
|
this.checkSMaskState();
|
||||||
break;
|
break;
|
||||||
case "TR":
|
case "TR":
|
||||||
this.current.transferMaps = value;
|
this.current.transferMaps = this.filterFactory
|
||||||
|
? this.filterFactory.addFilter(value)
|
||||||
|
: value;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2463,6 +2469,7 @@ class CanvasGraphics {
|
|||||||
this.commonObjs,
|
this.commonObjs,
|
||||||
this.objs,
|
this.objs,
|
||||||
this.canvasFactory,
|
this.canvasFactory,
|
||||||
|
this.filterFactory,
|
||||||
{
|
{
|
||||||
optionalContentConfig: this.optionalContentConfig,
|
optionalContentConfig: this.optionalContentConfig,
|
||||||
markedContentStack: this.markedContentStack,
|
markedContentStack: this.markedContentStack,
|
||||||
@ -3017,6 +3024,24 @@ class CanvasGraphics {
|
|||||||
this.paintInlineImageXObjectGroup(imgData, map);
|
this.paintInlineImageXObjectGroup(imgData, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyTransferMapsToBitmap(imgData) {
|
||||||
|
if (!this.current.transferMaps) {
|
||||||
|
return imgData.bitmap;
|
||||||
|
}
|
||||||
|
const { bitmap, width, height } = imgData;
|
||||||
|
const tmpCanvas = this.cachedCanvases.getCanvas(
|
||||||
|
"inlineImage",
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
const tmpCtx = tmpCanvas.context;
|
||||||
|
tmpCtx.filter = this.current.transferMaps;
|
||||||
|
tmpCtx.drawImage(bitmap, 0, 0);
|
||||||
|
tmpCtx.filter = "";
|
||||||
|
|
||||||
|
return tmpCanvas.canvas;
|
||||||
|
}
|
||||||
|
|
||||||
paintInlineImageXObject(imgData) {
|
paintInlineImageXObject(imgData) {
|
||||||
if (!this.contentVisible) {
|
if (!this.contentVisible) {
|
||||||
return;
|
return;
|
||||||
@ -3030,11 +3055,13 @@ class CanvasGraphics {
|
|||||||
ctx.scale(1 / width, -1 / height);
|
ctx.scale(1 / width, -1 / height);
|
||||||
|
|
||||||
let imgToPaint;
|
let imgToPaint;
|
||||||
// typeof check is needed due to node.js support, see issue #8489
|
if (imgData.bitmap) {
|
||||||
if (
|
imgToPaint = this.applyTransferMapsToBitmap(imgData);
|
||||||
|
} else if (
|
||||||
(typeof HTMLElement === "function" && imgData instanceof HTMLElement) ||
|
(typeof HTMLElement === "function" && imgData instanceof HTMLElement) ||
|
||||||
!imgData.data
|
!imgData.data
|
||||||
) {
|
) {
|
||||||
|
// typeof check is needed due to node.js support, see issue #8489
|
||||||
imgToPaint = imgData;
|
imgToPaint = imgData;
|
||||||
} else {
|
} else {
|
||||||
const tmpCanvas = this.cachedCanvases.getCanvas(
|
const tmpCanvas = this.cachedCanvases.getCanvas(
|
||||||
@ -3077,12 +3104,18 @@ class CanvasGraphics {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
const w = imgData.width;
|
let imgToPaint;
|
||||||
const h = imgData.height;
|
if (imgData.bitmap) {
|
||||||
|
imgToPaint = this.applyTransferMapsToBitmap(imgData);
|
||||||
|
} else {
|
||||||
|
const w = imgData.width;
|
||||||
|
const h = imgData.height;
|
||||||
|
|
||||||
const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
|
const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
|
||||||
const tmpCtx = tmpCanvas.context;
|
const tmpCtx = tmpCanvas.context;
|
||||||
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
|
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
|
||||||
|
imgToPaint = tmpCanvas.canvas;
|
||||||
|
}
|
||||||
|
|
||||||
for (const entry of map) {
|
for (const entry of map) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
@ -3090,7 +3123,7 @@ class CanvasGraphics {
|
|||||||
ctx.scale(1, -1);
|
ctx.scale(1, -1);
|
||||||
drawImageAtIntegerCoords(
|
drawImageAtIntegerCoords(
|
||||||
ctx,
|
ctx,
|
||||||
tmpCanvas.canvas,
|
imgToPaint,
|
||||||
entry.x,
|
entry.x,
|
||||||
entry.y,
|
entry.y,
|
||||||
entry.w,
|
entry.w,
|
||||||
|
@ -39,6 +39,139 @@ class PixelsPerInch {
|
|||||||
static PDF_TO_CSS_UNITS = this.CSS / this.PDF;
|
static PDF_TO_CSS_UNITS = this.CSS / this.PDF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FilterFactory aims to create some SVG filters we can use when drawing an
|
||||||
|
* image (or whatever) on a canvas.
|
||||||
|
* Filters aren't applied with ctx.putImageData because it just overwrites the
|
||||||
|
* underlying pixels.
|
||||||
|
* With these filters, it's possible for example to apply some transfer maps on
|
||||||
|
* an image without the need to apply them on the pixel arrays: the renderer
|
||||||
|
* does the magic for us.
|
||||||
|
*/
|
||||||
|
class FilterFactory {
|
||||||
|
#_cache;
|
||||||
|
|
||||||
|
#_defs;
|
||||||
|
|
||||||
|
#document;
|
||||||
|
|
||||||
|
#id = 0;
|
||||||
|
|
||||||
|
constructor({ ownerDocument = globalThis.document } = {}) {
|
||||||
|
this.#document = ownerDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
get #cache() {
|
||||||
|
return (this.#_cache ||= new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
get #defs() {
|
||||||
|
if (!this.#_defs) {
|
||||||
|
const svg = this.#document.createElementNS(SVG_NS, "svg");
|
||||||
|
svg.setAttribute("width", 0);
|
||||||
|
svg.setAttribute("height", 0);
|
||||||
|
svg.style.visibility = "hidden";
|
||||||
|
svg.style.contain = "strict";
|
||||||
|
this.#_defs = this.#document.createElementNS(SVG_NS, "defs");
|
||||||
|
svg.append(this.#_defs);
|
||||||
|
this.#document.body.append(svg);
|
||||||
|
}
|
||||||
|
return this.#_defs;
|
||||||
|
}
|
||||||
|
|
||||||
|
addFilter(maps) {
|
||||||
|
if (!maps) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a page is zoomed the page is re-drawn but the maps are likely
|
||||||
|
// the same.
|
||||||
|
let value = this.#cache.get(maps);
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tableR, tableG, tableB, key;
|
||||||
|
if (maps.length === 1) {
|
||||||
|
const mapR = maps[0];
|
||||||
|
const buffer = new Array(256);
|
||||||
|
for (let i = 0; i < 256; i++) {
|
||||||
|
buffer[i] = mapR[i] / 255;
|
||||||
|
}
|
||||||
|
key = tableR = tableG = tableB = buffer.join(",");
|
||||||
|
} else {
|
||||||
|
const [mapR, mapG, mapB] = maps;
|
||||||
|
const bufferR = new Array(256);
|
||||||
|
const bufferG = new Array(256);
|
||||||
|
const bufferB = new Array(256);
|
||||||
|
for (let i = 0; i < 256; i++) {
|
||||||
|
bufferR[i] = mapR[i] / 255;
|
||||||
|
bufferG[i] = mapG[i] / 255;
|
||||||
|
bufferB[i] = mapB[i] / 255;
|
||||||
|
}
|
||||||
|
tableR = bufferR.join(",");
|
||||||
|
tableG = bufferG.join(",");
|
||||||
|
tableB = bufferB.join(",");
|
||||||
|
key = `${tableR}${tableG}${tableB}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = this.#cache.get(key);
|
||||||
|
if (value) {
|
||||||
|
this.#cache.set(maps, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We create a SVG filter: feComponentTransferElement
|
||||||
|
// https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement
|
||||||
|
|
||||||
|
const id = `transfer_map_${this.#id++}`;
|
||||||
|
const url = `url(#${id})`;
|
||||||
|
this.#cache.set(maps, url);
|
||||||
|
this.#cache.set(key, url);
|
||||||
|
|
||||||
|
const filter = this.#document.createElementNS(SVG_NS, "filter", SVG_NS);
|
||||||
|
filter.setAttribute("id", id);
|
||||||
|
filter.setAttribute("color-interpolation-filters", "sRGB");
|
||||||
|
const feComponentTransfer = this.#document.createElementNS(
|
||||||
|
SVG_NS,
|
||||||
|
"feComponentTransfer"
|
||||||
|
);
|
||||||
|
filter.append(feComponentTransfer);
|
||||||
|
|
||||||
|
const type = "discrete";
|
||||||
|
const feFuncR = this.#document.createElementNS(SVG_NS, "feFuncR");
|
||||||
|
feFuncR.setAttribute("type", type);
|
||||||
|
feFuncR.setAttribute("tableValues", tableR);
|
||||||
|
feComponentTransfer.append(feFuncR);
|
||||||
|
|
||||||
|
const feFuncG = this.#document.createElementNS(SVG_NS, "feFuncG");
|
||||||
|
feFuncG.setAttribute("type", type);
|
||||||
|
feFuncG.setAttribute("tableValues", tableG);
|
||||||
|
feComponentTransfer.append(feFuncG);
|
||||||
|
|
||||||
|
const feFuncB = this.#document.createElementNS(SVG_NS, "feFuncB");
|
||||||
|
feFuncB.setAttribute("type", type);
|
||||||
|
feFuncB.setAttribute("tableValues", tableB);
|
||||||
|
feComponentTransfer.append(feFuncB);
|
||||||
|
|
||||||
|
this.#defs.append(filter);
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.#_defs) {
|
||||||
|
this.#_defs.parentNode.remove();
|
||||||
|
this.#_defs = null;
|
||||||
|
}
|
||||||
|
if (this.#_cache) {
|
||||||
|
this.#_cache.clear();
|
||||||
|
this.#_cache = null;
|
||||||
|
}
|
||||||
|
this.#id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DOMCanvasFactory extends BaseCanvasFactory {
|
class DOMCanvasFactory extends BaseCanvasFactory {
|
||||||
constructor({ ownerDocument = globalThis.document } = {}) {
|
constructor({ ownerDocument = globalThis.document } = {}) {
|
||||||
super();
|
super();
|
||||||
@ -681,6 +814,7 @@ export {
|
|||||||
DOMCMapReaderFactory,
|
DOMCMapReaderFactory,
|
||||||
DOMStandardFontDataFactory,
|
DOMStandardFontDataFactory,
|
||||||
DOMSVGFactory,
|
DOMSVGFactory,
|
||||||
|
FilterFactory,
|
||||||
getColorValues,
|
getColorValues,
|
||||||
getCurrentTransform,
|
getCurrentTransform,
|
||||||
getCurrentTransformInverse,
|
getCurrentTransformInverse,
|
||||||
|
@ -52,6 +52,7 @@ import {
|
|||||||
version,
|
version,
|
||||||
} from "./display/api.js";
|
} from "./display/api.js";
|
||||||
import {
|
import {
|
||||||
|
FilterFactory,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
getXfaPageViewport,
|
getXfaPageViewport,
|
||||||
@ -91,6 +92,7 @@ export {
|
|||||||
createPromiseCapability,
|
createPromiseCapability,
|
||||||
createValidAbsoluteUrl,
|
createValidAbsoluteUrl,
|
||||||
FeatureTest,
|
FeatureTest,
|
||||||
|
FilterFactory,
|
||||||
getDocument,
|
getDocument,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
|
@ -13,23 +13,37 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FeatureTest } from "./util.js";
|
import { FeatureTest, ImageKind } from "./util.js";
|
||||||
|
|
||||||
function applyMaskImageData({
|
function convertToRGBA(params) {
|
||||||
|
switch (params.kind) {
|
||||||
|
case ImageKind.GRAYSCALE_1BPP:
|
||||||
|
return convertBlackAndWhiteToRGBA(params);
|
||||||
|
case ImageKind.RGB_24BPP:
|
||||||
|
return convertRGBToRGBA(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertBlackAndWhiteToRGBA({
|
||||||
src,
|
src,
|
||||||
srcPos = 0,
|
srcPos = 0,
|
||||||
dest,
|
dest,
|
||||||
destPos = 0,
|
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
nonBlackColor = 0xffffffff,
|
||||||
inverseDecode = false,
|
inverseDecode = false,
|
||||||
}) {
|
}) {
|
||||||
const opaque = FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
|
const black = FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
|
||||||
const [zeroMapping, oneMapping] = !inverseDecode ? [opaque, 0] : [0, opaque];
|
const [zeroMapping, oneMapping] = inverseDecode
|
||||||
|
? [nonBlackColor, black]
|
||||||
|
: [black, nonBlackColor];
|
||||||
const widthInSource = width >> 3;
|
const widthInSource = width >> 3;
|
||||||
const widthRemainder = width & 7;
|
const widthRemainder = width & 7;
|
||||||
const srcLength = src.length;
|
const srcLength = src.length;
|
||||||
dest = new Uint32Array(dest.buffer);
|
dest = new Uint32Array(dest.buffer);
|
||||||
|
let destPos = 0;
|
||||||
|
|
||||||
for (let i = 0; i < height; i++) {
|
for (let i = 0; i < height; i++) {
|
||||||
for (const max = srcPos + widthInSource; srcPos < max; srcPos++) {
|
for (const max = srcPos + widthInSource; srcPos < max; srcPos++) {
|
||||||
@ -51,8 +65,70 @@ function applyMaskImageData({
|
|||||||
dest[destPos++] = elem & (1 << (7 - j)) ? oneMapping : zeroMapping;
|
dest[destPos++] = elem & (1 << (7 - j)) ? oneMapping : zeroMapping;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return { srcPos, destPos };
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertRGBToRGBA({
|
||||||
|
src,
|
||||||
|
srcPos = 0,
|
||||||
|
dest,
|
||||||
|
destPos = 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}) {
|
||||||
|
let i = 0;
|
||||||
|
const len32 = src.length >> 2;
|
||||||
|
const src32 = new Uint32Array(src.buffer, srcPos, len32);
|
||||||
|
|
||||||
|
if (FeatureTest.isLittleEndian) {
|
||||||
|
// It's a way faster to do the shuffle manually instead of working
|
||||||
|
// component by component with some Uint8 arrays.
|
||||||
|
for (; i < len32 - 2; i += 3, destPos += 4) {
|
||||||
|
const s1 = src32[i]; // R2B1G1R1
|
||||||
|
const s2 = src32[i + 1]; // G3R3B2G2
|
||||||
|
const s3 = src32[i + 2]; // B4G4R4B3
|
||||||
|
|
||||||
|
dest[destPos] = s1 | 0xff000000;
|
||||||
|
dest[destPos + 1] = (s1 >>> 24) | (s2 << 8) | 0xff000000;
|
||||||
|
dest[destPos + 2] = (s2 >>> 16) | (s3 << 16) | 0xff000000;
|
||||||
|
dest[destPos + 3] = (s3 >>> 8) | 0xff000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = i * 4, jj = src.length; j < jj; j += 3) {
|
||||||
|
dest[destPos++] =
|
||||||
|
src[j] | (src[j + 1] << 8) | (src[j + 2] << 16) | 0xff000000;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (; i < len32 - 2; i += 3, destPos += 4) {
|
||||||
|
const s1 = src32[i]; // R1G1B1R2
|
||||||
|
const s2 = src32[i + 1]; // G2B2R3G3
|
||||||
|
const s3 = src32[i + 2]; // B3R4G4B4
|
||||||
|
|
||||||
|
dest[destPos] = s1 | 0xff;
|
||||||
|
dest[destPos + 1] = (s1 << 24) | (s2 >>> 8) | 0xff;
|
||||||
|
dest[destPos + 2] = (s2 << 16) | (s3 >>> 16) | 0xff;
|
||||||
|
dest[destPos + 3] = (s3 << 8) | 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = i * 4, jj = src.length; j < jj; j += 3) {
|
||||||
|
dest[destPos++] =
|
||||||
|
(src[j] << 24) | (src[j + 1] << 16) | (src[j + 2] << 8) | 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { srcPos, destPos };
|
return { srcPos, destPos };
|
||||||
}
|
}
|
||||||
|
|
||||||
export { applyMaskImageData };
|
function grayToRGBA(src, dest) {
|
||||||
|
if (FeatureTest.isLittleEndian) {
|
||||||
|
for (let i = 0, ii = src.length; i < ii; i++) {
|
||||||
|
dest[i] = (src[i] * 0x10101) | 0xff000000;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0, ii = src.length; i < ii; i++) {
|
||||||
|
dest[i] = (src[i] * 0x1010100) | 0x000000ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { convertBlackAndWhiteToRGBA, convertToRGBA, grayToRGBA };
|
||||||
|
@ -2655,7 +2655,11 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("gets operatorList with JPEG image (issue 4888)", async function () {
|
it("gets operatorList with JPEG image (issue 4888)", async function () {
|
||||||
const loadingTask = getDocument(buildGetDocumentParams("cmykjpeg.pdf"));
|
const loadingTask = getDocument(
|
||||||
|
buildGetDocumentParams("cmykjpeg.pdf", {
|
||||||
|
isOffscreenCanvasSupported: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const pdfDoc = await loadingTask.promise;
|
const pdfDoc = await loadingTask.promise;
|
||||||
const pdfPage = await pdfDoc.getPage(1);
|
const pdfPage = await pdfDoc.getPage(1);
|
||||||
@ -3089,7 +3093,11 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
|||||||
EXPECTED_WIDTH = 2550,
|
EXPECTED_WIDTH = 2550,
|
||||||
EXPECTED_HEIGHT = 3300;
|
EXPECTED_HEIGHT = 3300;
|
||||||
|
|
||||||
const loadingTask = getDocument(buildGetDocumentParams("issue11878.pdf"));
|
const loadingTask = getDocument(
|
||||||
|
buildGetDocumentParams("issue11878.pdf", {
|
||||||
|
isOffscreenCanvasSupported: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
const pdfDoc = await loadingTask.promise;
|
const pdfDoc = await loadingTask.promise;
|
||||||
let firstImgData = null;
|
let firstImgData = null;
|
||||||
|
|
||||||
|
@ -61,7 +61,11 @@ describe("SVGGraphics", function () {
|
|||||||
let page;
|
let page;
|
||||||
|
|
||||||
beforeAll(async function () {
|
beforeAll(async function () {
|
||||||
loadingTask = getDocument(buildGetDocumentParams("xobject-image.pdf"));
|
loadingTask = getDocument(
|
||||||
|
buildGetDocumentParams("xobject-image.pdf", {
|
||||||
|
isOffscreenCanvasSupported: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
const doc = await loadingTask.promise;
|
const doc = await loadingTask.promise;
|
||||||
page = await doc.getPage(1);
|
page = await doc.getPage(1);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user