Merge pull request #16062 from calixteman/create_image_in_worker
[api-minor] Generate images in the worker instead of the main thread.
This commit is contained in:
commit
d4216264e8
@ -425,6 +425,8 @@ class Page {
|
||||
this.resources,
|
||||
this.nonBlendModesSet
|
||||
),
|
||||
isOffscreenCanvasSupported:
|
||||
this.evaluatorOptions.isOffscreenCanvasSupported,
|
||||
pageIndex: this.pageIndex,
|
||||
cacheKey,
|
||||
});
|
||||
|
@ -716,7 +716,12 @@ class PartialEvaluator {
|
||||
});
|
||||
// We force the use of RGBA_32BPP images here, because we can't handle
|
||||
// any other kind.
|
||||
imgData = imageObj.createImageData(/* forceRGBA = */ true);
|
||||
imgData = imageObj.createImageData(
|
||||
/* forceRGBA = */ true,
|
||||
/* isOffscreenCanvasSupported = */ false
|
||||
);
|
||||
operatorList.isOffscreenCanvasSupported =
|
||||
this.options.isOffscreenCanvasSupported;
|
||||
operatorList.addImageOps(
|
||||
OPS.paintInlineImageXObject,
|
||||
[imgData],
|
||||
@ -756,11 +761,22 @@ class PartialEvaluator {
|
||||
localColorSpaceCache,
|
||||
})
|
||||
.then(imageObj => {
|
||||
imgData = imageObj.createImageData(/* forceRGBA = */ false);
|
||||
imgData = imageObj.createImageData(
|
||||
/* forceRGBA = */ false,
|
||||
/* isOffscreenCanvasSupported = */ this.options
|
||||
.isOffscreenCanvasSupported
|
||||
);
|
||||
|
||||
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);
|
||||
})
|
||||
.catch(reason => {
|
||||
|
@ -13,8 +13,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert, FormatError, ImageKind, info, warn } from "../shared/util.js";
|
||||
import { applyMaskImageData } from "../shared/image_utils.js";
|
||||
import {
|
||||
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 { ColorSpace } from "./colorspace.js";
|
||||
import { DecodeStream } from "./decode_stream.js";
|
||||
@ -364,11 +374,12 @@ class PDFImage {
|
||||
const canvas = new OffscreenCanvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
const imgData = ctx.createImageData(width, height);
|
||||
applyMaskImageData({
|
||||
convertBlackAndWhiteToRGBA({
|
||||
src: imgArray,
|
||||
dest: imgData.data,
|
||||
width,
|
||||
height,
|
||||
nonBlackColor: 0,
|
||||
inverseDecode,
|
||||
});
|
||||
|
||||
@ -641,7 +652,7 @@ class PDFImage {
|
||||
}
|
||||
}
|
||||
|
||||
createImageData(forceRGBA = false) {
|
||||
createImageData(forceRGBA = false, isOffscreenCanvasSupported = false) {
|
||||
const drawWidth = this.drawWidth;
|
||||
const drawHeight = this.drawHeight;
|
||||
const imgData = {
|
||||
@ -686,8 +697,12 @@ class PDFImage {
|
||||
drawWidth === originalWidth &&
|
||||
drawHeight === originalHeight
|
||||
) {
|
||||
const data = this.getImageBytes(originalHeight * rowBytes, {});
|
||||
if (isOffscreenCanvasSupported) {
|
||||
return this.createBitmap(kind, originalWidth, originalHeight, data);
|
||||
}
|
||||
imgData.kind = kind;
|
||||
imgData.data = this.getImageBytes(originalHeight * rowBytes, {});
|
||||
imgData.data = data;
|
||||
|
||||
if (this.needsDecode) {
|
||||
// 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) {
|
||||
let imageLength = originalHeight * rowBytes;
|
||||
switch (this.colorSpace.name) {
|
||||
case "DeviceGray":
|
||||
// Avoid truncating the image, since `JpegImage.getData`
|
||||
// will expand the image data when `forceRGB === true`.
|
||||
imageLength *= 3;
|
||||
/* falls through */
|
||||
case "DeviceRGB":
|
||||
case "DeviceCMYK":
|
||||
imgData.kind = ImageKind.RGB_24BPP;
|
||||
imgData.data = this.getImageBytes(imageLength, {
|
||||
if (isOffscreenCanvasSupported) {
|
||||
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 rgba = this.getImageBytes(imageLength, {
|
||||
drawWidth,
|
||||
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
|
||||
// more compact RGB_24BPP form if allowable.
|
||||
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) {
|
||||
imgData.kind = ImageKind.RGB_24BPP;
|
||||
imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 3);
|
||||
alpha01 = 0;
|
||||
if (!isOffscreenCanvasSupported) {
|
||||
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 {
|
||||
imgData.kind = ImageKind.RGBA_32BPP;
|
||||
imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
|
||||
if (!isOffscreenCanvasSupported) {
|
||||
data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
|
||||
}
|
||||
|
||||
alpha01 = 1;
|
||||
maybeUndoPreblend = true;
|
||||
|
||||
// Color key masking (opacity) must be performed before decoding.
|
||||
this.fillOpacity(
|
||||
imgData.data,
|
||||
drawWidth,
|
||||
drawHeight,
|
||||
actualHeight,
|
||||
comps
|
||||
);
|
||||
this.fillOpacity(data, drawWidth, drawHeight, actualHeight, comps);
|
||||
}
|
||||
|
||||
if (this.needsDecode) {
|
||||
this.decodeBuffer(comps);
|
||||
}
|
||||
this.colorSpace.fillRgb(
|
||||
imgData.data,
|
||||
data,
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
drawWidth,
|
||||
@ -771,9 +830,23 @@ class PDFImage {
|
||||
alpha01
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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(
|
||||
length,
|
||||
{ drawWidth, drawHeight, forceRGB = false, internal = false }
|
||||
{
|
||||
drawWidth,
|
||||
drawHeight,
|
||||
forceRGBA = false,
|
||||
forceRGB = false,
|
||||
internal = false,
|
||||
}
|
||||
) {
|
||||
this.image.reset();
|
||||
this.image.drawWidth = drawWidth || this.width;
|
||||
this.image.drawHeight = drawHeight || this.height;
|
||||
this.image.forceRGBA = !!forceRGBA;
|
||||
this.image.forceRGB = !!forceRGB;
|
||||
const imageBytes = this.image.getBytes(length);
|
||||
|
||||
|
@ -63,7 +63,7 @@ class JpegStream extends DecodeStream {
|
||||
|
||||
// Checking if values need to be transformed before conversion.
|
||||
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 decodeArrLength = decodeArr.length;
|
||||
const transform = new Int32Array(decodeArrLength);
|
||||
@ -93,6 +93,7 @@ class JpegStream extends DecodeStream {
|
||||
const data = jpegImage.getData({
|
||||
width: this.drawWidth,
|
||||
height: this.drawHeight,
|
||||
forceRGBA: this.forceRGBA,
|
||||
forceRGB: this.forceRGB,
|
||||
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 { grayToRGBA } from "../shared/image_utils.js";
|
||||
import { readUint16 } from "./core_utils.js";
|
||||
|
||||
class JpegError extends BaseException {
|
||||
@ -1217,6 +1218,19 @@ class JpegImage {
|
||||
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) {
|
||||
let Y, Cb, Cr, k;
|
||||
let offset = 0;
|
||||
@ -1287,6 +1301,74 @@ class JpegImage {
|
||||
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) {
|
||||
let Y, Cb, Cr;
|
||||
for (let i = 0, length = data.length; i < length; i += 4) {
|
||||
@ -1371,7 +1453,81 @@ class JpegImage {
|
||||
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 (
|
||||
typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || TESTING")
|
||||
@ -1387,23 +1543,37 @@ class JpegImage {
|
||||
// Type of data: Uint8ClampedArray(width * height * numComponents)
|
||||
const data = this._getLinearizedBlockData(width, height, isSourcePDF);
|
||||
|
||||
if (this.numComponents === 1 && forceRGB) {
|
||||
const rgbData = new Uint8ClampedArray(data.length * 3);
|
||||
if (this.numComponents === 1 && (forceRGBA || forceRGB)) {
|
||||
const len = data.length * (forceRGBA ? 4 : 3);
|
||||
const rgbaData = new Uint8ClampedArray(len);
|
||||
let offset = 0;
|
||||
for (const grayColor of data) {
|
||||
rgbData[offset++] = grayColor;
|
||||
rgbData[offset++] = grayColor;
|
||||
rgbData[offset++] = grayColor;
|
||||
if (forceRGBA) {
|
||||
grayToRGBA(data, new Uint32Array(rgbaData.buffer));
|
||||
} else {
|
||||
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) {
|
||||
if (forceRGBA) {
|
||||
const rgbaData = new Uint8ClampedArray((data.length / 3) * 4);
|
||||
return this._convertYccToRgba(data, rgbaData);
|
||||
}
|
||||
return this._convertYccToRgb(data);
|
||||
} else if (this.numComponents === 4) {
|
||||
if (this._isColorConversionNeeded) {
|
||||
if (forceRGBA) {
|
||||
return this._convertYcckToRgba(data);
|
||||
}
|
||||
if (forceRGB) {
|
||||
return this._convertYcckToRgb(data);
|
||||
}
|
||||
return this._convertYcckToCmyk(data);
|
||||
} else if (forceRGBA) {
|
||||
return this._convertCmykToRgba(data);
|
||||
} else if (forceRGB) {
|
||||
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.
|
||||
fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup);
|
||||
argsArray.splice(iFirstSave, count * 4, [
|
||||
{
|
||||
width: imgWidth,
|
||||
height: imgHeight,
|
||||
kind: ImageKind.RGBA_32BPP,
|
||||
data: imgData,
|
||||
},
|
||||
map,
|
||||
]);
|
||||
argsArray.splice(iFirstSave, count * 4, [img, map]);
|
||||
|
||||
return iFirstSave + 1;
|
||||
}
|
||||
@ -487,11 +502,17 @@ class QueueOptimizer extends NullOptimizer {
|
||||
iCurr: 0,
|
||||
fnArray: queue.fnArray,
|
||||
argsArray: queue.argsArray,
|
||||
isOffscreenCanvasSupported: false,
|
||||
};
|
||||
this.match = null;
|
||||
this.lastProcessed = 0;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line accessor-pairs
|
||||
set isOffscreenCanvasSupported(value) {
|
||||
this.context.isOffscreenCanvasSupported = value;
|
||||
}
|
||||
|
||||
_optimize() {
|
||||
// Process new fnArray item(s) chunk.
|
||||
const fnArray = this.queue.fnArray;
|
||||
@ -589,6 +610,11 @@ class OperatorList {
|
||||
this._resolved = streamSink ? null : Promise.resolve();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line accessor-pairs
|
||||
set isOffscreenCanvasSupported(value) {
|
||||
this.optimizer.isOffscreenCanvasSupported = value;
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.argsArray.length;
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ import {
|
||||
DOMCanvasFactory,
|
||||
DOMCMapReaderFactory,
|
||||
DOMStandardFontDataFactory,
|
||||
FilterFactory,
|
||||
isDataScheme,
|
||||
isValidFetchUrl,
|
||||
loadScript,
|
||||
@ -232,6 +233,8 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) {
|
||||
* (see `web/debugger.js`). The default value is `false`.
|
||||
* @property {Object} [canvasFactory] - The factory instance that will be used
|
||||
* 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));
|
||||
const canvasFactory =
|
||||
src.canvasFactory || new DefaultCanvasFactory({ ownerDocument });
|
||||
const filterFactory =
|
||||
src.filterFactory || new FilterFactory({ ownerDocument });
|
||||
|
||||
// Parameters only intended for development/testing purposes.
|
||||
const styleElement =
|
||||
@ -355,6 +360,7 @@ function getDocument(src) {
|
||||
// since the user may provide *custom* ones.
|
||||
const transportFactory = {
|
||||
canvasFactory,
|
||||
filterFactory,
|
||||
};
|
||||
if (!useWorkerFetch) {
|
||||
transportFactory.cMapReaderFactory = new CMapReaderFactory({
|
||||
@ -1514,6 +1520,7 @@ class PDFPageProxy {
|
||||
operatorList: intentState.operatorList,
|
||||
pageIndex: this._pageIndex,
|
||||
canvasFactory: canvasFactory || this._transport.canvasFactory,
|
||||
filterFactory: this._transport.filterFactory,
|
||||
useRequestAnimationFrame: !intentPrint,
|
||||
pdfBug: this._pdfBug,
|
||||
pageColors,
|
||||
@ -1526,19 +1533,25 @@ class PDFPageProxy {
|
||||
intentState.displayReadyCapability.promise,
|
||||
optionalContentConfigPromise,
|
||||
])
|
||||
.then(([transparency, optionalContentConfig]) => {
|
||||
if (this.pendingCleanup) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
this._stats?.time("Rendering");
|
||||
|
||||
internalRenderTask.initializeGraphics({
|
||||
transparency,
|
||||
.then(
|
||||
([
|
||||
{ transparency, isOffscreenCanvasSupported },
|
||||
optionalContentConfig,
|
||||
});
|
||||
internalRenderTask.operatorListChanged();
|
||||
})
|
||||
]) => {
|
||||
if (this.pendingCleanup) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
this._stats?.time("Rendering");
|
||||
|
||||
internalRenderTask.initializeGraphics({
|
||||
transparency,
|
||||
isOffscreenCanvasSupported,
|
||||
optionalContentConfig,
|
||||
});
|
||||
internalRenderTask.operatorListChanged();
|
||||
}
|
||||
)
|
||||
.catch(complete);
|
||||
|
||||
return renderTask;
|
||||
@ -1739,7 +1752,7 @@ class PDFPageProxy {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_startRenderPage(transparency, cacheKey) {
|
||||
_startRenderPage(transparency, isOffscreenCanvasSupported, cacheKey) {
|
||||
const intentState = this._intentStates.get(cacheKey);
|
||||
if (!intentState) {
|
||||
return; // Rendering was cancelled.
|
||||
@ -1748,7 +1761,10 @@ class PDFPageProxy {
|
||||
|
||||
// TODO Refactor RenderPageRequest to separate rendering
|
||||
// and operator list logic
|
||||
intentState.displayReadyCapability?.resolve(transparency);
|
||||
intentState.displayReadyCapability?.resolve({
|
||||
transparency,
|
||||
isOffscreenCanvasSupported,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2357,6 +2373,7 @@ class WorkerTransport {
|
||||
this._params = params;
|
||||
|
||||
this.canvasFactory = factory.canvasFactory;
|
||||
this.filterFactory = factory.filterFactory;
|
||||
this.cMapReaderFactory = factory.cMapReaderFactory;
|
||||
this.standardFontDataFactory = factory.standardFontDataFactory;
|
||||
|
||||
@ -2489,6 +2506,7 @@ class WorkerTransport {
|
||||
this.commonObjs.clear();
|
||||
this.fontLoader.clear();
|
||||
this.#methodPromises.clear();
|
||||
this.filterFactory.destroy();
|
||||
|
||||
if (this._networkStream) {
|
||||
this._networkStream.cancelAllRequests(
|
||||
@ -2709,7 +2727,11 @@ class WorkerTransport {
|
||||
}
|
||||
|
||||
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]) => {
|
||||
@ -3079,6 +3101,7 @@ class WorkerTransport {
|
||||
this.fontLoader.clear();
|
||||
}
|
||||
this.#methodPromises.clear();
|
||||
this.filterFactory.destroy();
|
||||
}
|
||||
|
||||
get loadingParams() {
|
||||
@ -3246,6 +3269,7 @@ class InternalRenderTask {
|
||||
operatorList,
|
||||
pageIndex,
|
||||
canvasFactory,
|
||||
filterFactory,
|
||||
useRequestAnimationFrame = false,
|
||||
pdfBug = false,
|
||||
pageColors = null,
|
||||
@ -3259,6 +3283,7 @@ class InternalRenderTask {
|
||||
this.operatorList = operatorList;
|
||||
this._pageIndex = pageIndex;
|
||||
this.canvasFactory = canvasFactory;
|
||||
this.filterFactory = filterFactory;
|
||||
this._pdfBug = pdfBug;
|
||||
this.pageColors = pageColors;
|
||||
|
||||
@ -3285,7 +3310,11 @@ class InternalRenderTask {
|
||||
});
|
||||
}
|
||||
|
||||
initializeGraphics({ transparency = false, optionalContentConfig }) {
|
||||
initializeGraphics({
|
||||
transparency = false,
|
||||
isOffscreenCanvasSupported = false,
|
||||
optionalContentConfig,
|
||||
}) {
|
||||
if (this.cancelled) {
|
||||
return;
|
||||
}
|
||||
@ -3312,6 +3341,7 @@ class InternalRenderTask {
|
||||
this.commonObjs,
|
||||
this.objs,
|
||||
this.canvasFactory,
|
||||
isOffscreenCanvasSupported ? this.filterFactory : null,
|
||||
{ optionalContentConfig },
|
||||
this.annotationCanvasMap,
|
||||
this.pageColors
|
||||
|
@ -37,7 +37,7 @@ import {
|
||||
PathType,
|
||||
TilingPattern,
|
||||
} 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.
|
||||
// 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
|
||||
// inversion has already been handled.
|
||||
|
||||
({ srcPos } = applyMaskImageData({
|
||||
({ srcPos } = convertBlackAndWhiteToRGBA({
|
||||
src,
|
||||
srcPos,
|
||||
dest,
|
||||
width,
|
||||
height: thisChunkHeight,
|
||||
nonBlackColor: 0,
|
||||
}));
|
||||
|
||||
ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
|
||||
@ -1015,6 +1016,7 @@ class CanvasGraphics {
|
||||
commonObjs,
|
||||
objs,
|
||||
canvasFactory,
|
||||
filterFactory,
|
||||
{ optionalContentConfig, markedContentStack = null },
|
||||
annotationCanvasMap,
|
||||
pageColors
|
||||
@ -1032,6 +1034,7 @@ class CanvasGraphics {
|
||||
this.commonObjs = commonObjs;
|
||||
this.objs = objs;
|
||||
this.canvasFactory = canvasFactory;
|
||||
this.filterFactory = filterFactory;
|
||||
this.groupStack = [];
|
||||
this.processingType3 = null;
|
||||
// Patterns are painted relative to the initial page/form transform, see
|
||||
@ -1573,7 +1576,10 @@ class CanvasGraphics {
|
||||
this.checkSMaskState();
|
||||
break;
|
||||
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.objs,
|
||||
this.canvasFactory,
|
||||
this.filterFactory,
|
||||
{
|
||||
optionalContentConfig: this.optionalContentConfig,
|
||||
markedContentStack: this.markedContentStack,
|
||||
@ -3017,6 +3024,24 @@ class CanvasGraphics {
|
||||
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) {
|
||||
if (!this.contentVisible) {
|
||||
return;
|
||||
@ -3030,11 +3055,13 @@ class CanvasGraphics {
|
||||
ctx.scale(1 / width, -1 / height);
|
||||
|
||||
let imgToPaint;
|
||||
// typeof check is needed due to node.js support, see issue #8489
|
||||
if (
|
||||
if (imgData.bitmap) {
|
||||
imgToPaint = this.applyTransferMapsToBitmap(imgData);
|
||||
} else if (
|
||||
(typeof HTMLElement === "function" && imgData instanceof HTMLElement) ||
|
||||
!imgData.data
|
||||
) {
|
||||
// typeof check is needed due to node.js support, see issue #8489
|
||||
imgToPaint = imgData;
|
||||
} else {
|
||||
const tmpCanvas = this.cachedCanvases.getCanvas(
|
||||
@ -3077,12 +3104,18 @@ class CanvasGraphics {
|
||||
return;
|
||||
}
|
||||
const ctx = this.ctx;
|
||||
const w = imgData.width;
|
||||
const h = imgData.height;
|
||||
let imgToPaint;
|
||||
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 tmpCtx = tmpCanvas.context;
|
||||
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
|
||||
const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
|
||||
const tmpCtx = tmpCanvas.context;
|
||||
putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
|
||||
imgToPaint = tmpCanvas.canvas;
|
||||
}
|
||||
|
||||
for (const entry of map) {
|
||||
ctx.save();
|
||||
@ -3090,7 +3123,7 @@ class CanvasGraphics {
|
||||
ctx.scale(1, -1);
|
||||
drawImageAtIntegerCoords(
|
||||
ctx,
|
||||
tmpCanvas.canvas,
|
||||
imgToPaint,
|
||||
entry.x,
|
||||
entry.y,
|
||||
entry.w,
|
||||
|
@ -39,6 +39,139 @@ class PixelsPerInch {
|
||||
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 {
|
||||
constructor({ ownerDocument = globalThis.document } = {}) {
|
||||
super();
|
||||
@ -681,6 +814,7 @@ export {
|
||||
DOMCMapReaderFactory,
|
||||
DOMStandardFontDataFactory,
|
||||
DOMSVGFactory,
|
||||
FilterFactory,
|
||||
getColorValues,
|
||||
getCurrentTransform,
|
||||
getCurrentTransformInverse,
|
||||
|
@ -52,6 +52,7 @@ import {
|
||||
version,
|
||||
} from "./display/api.js";
|
||||
import {
|
||||
FilterFactory,
|
||||
getFilenameFromUrl,
|
||||
getPdfFilenameFromUrl,
|
||||
getXfaPageViewport,
|
||||
@ -91,6 +92,7 @@ export {
|
||||
createPromiseCapability,
|
||||
createValidAbsoluteUrl,
|
||||
FeatureTest,
|
||||
FilterFactory,
|
||||
getDocument,
|
||||
getFilenameFromUrl,
|
||||
getPdfFilenameFromUrl,
|
||||
|
@ -13,23 +13,37 @@
|
||||
* 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,
|
||||
srcPos = 0,
|
||||
dest,
|
||||
destPos = 0,
|
||||
width,
|
||||
height,
|
||||
nonBlackColor = 0xffffffff,
|
||||
inverseDecode = false,
|
||||
}) {
|
||||
const opaque = FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
|
||||
const [zeroMapping, oneMapping] = !inverseDecode ? [opaque, 0] : [0, opaque];
|
||||
const black = FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
|
||||
const [zeroMapping, oneMapping] = inverseDecode
|
||||
? [nonBlackColor, black]
|
||||
: [black, nonBlackColor];
|
||||
const widthInSource = width >> 3;
|
||||
const widthRemainder = width & 7;
|
||||
const srcLength = src.length;
|
||||
dest = new Uint32Array(dest.buffer);
|
||||
let destPos = 0;
|
||||
|
||||
for (let i = 0; i < height; i++) {
|
||||
for (const max = srcPos + widthInSource; srcPos < max; srcPos++) {
|
||||
@ -51,8 +65,70 @@ function applyMaskImageData({
|
||||
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 };
|
||||
}
|
||||
|
||||
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 () {
|
||||
const loadingTask = getDocument(buildGetDocumentParams("cmykjpeg.pdf"));
|
||||
const loadingTask = getDocument(
|
||||
buildGetDocumentParams("cmykjpeg.pdf", {
|
||||
isOffscreenCanvasSupported: false,
|
||||
})
|
||||
);
|
||||
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
const pdfPage = await pdfDoc.getPage(1);
|
||||
@ -3089,7 +3093,11 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
EXPECTED_WIDTH = 2550,
|
||||
EXPECTED_HEIGHT = 3300;
|
||||
|
||||
const loadingTask = getDocument(buildGetDocumentParams("issue11878.pdf"));
|
||||
const loadingTask = getDocument(
|
||||
buildGetDocumentParams("issue11878.pdf", {
|
||||
isOffscreenCanvasSupported: false,
|
||||
})
|
||||
);
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
let firstImgData = null;
|
||||
|
||||
|
@ -61,7 +61,11 @@ describe("SVGGraphics", function () {
|
||||
let page;
|
||||
|
||||
beforeAll(async function () {
|
||||
loadingTask = getDocument(buildGetDocumentParams("xobject-image.pdf"));
|
||||
loadingTask = getDocument(
|
||||
buildGetDocumentParams("xobject-image.pdf", {
|
||||
isOffscreenCanvasSupported: false,
|
||||
})
|
||||
);
|
||||
const doc = await loadingTask.promise;
|
||||
page = await doc.getPage(1);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user