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.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…
x
Reference in New Issue
Block a user