Skip to content

Commit fd03cd5

Browse files
committed
[api-minor] Generate images in the worker instead of the main thread.
We introduced the use of OffscreenCanvas in mozilla#14754 and this patch aims to use them for all kind of images. It'll slightly improve performances (and maybe slightly decrease memory use). Since an image can be rendered in using some transfer maps but because of OffscreenCanvas we don't have the underlying pixels array the transfer maps stuff is re-implemented in using the SVG filter feComponentTransfer.
1 parent 9640add commit fd03cd5

13 files changed

+700
-89
lines changed

src/core/document.js

+2
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ class Page {
425425
this.resources,
426426
this.nonBlendModesSet
427427
),
428+
isOffscreenCanvasSupported:
429+
this.evaluatorOptions.isOffscreenCanvasSupported,
428430
pageIndex: this.pageIndex,
429431
cacheKey,
430432
});

src/core/evaluator.js

+19-3
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,12 @@ class PartialEvaluator {
716716
});
717717
// We force the use of RGBA_32BPP images here, because we can't handle
718718
// any other kind.
719-
imgData = imageObj.createImageData(/* forceRGBA = */ true);
719+
imgData = imageObj.createImageData(
720+
/* forceRGBA = */ true,
721+
/* isOffscreenCanvasSupported = */ false
722+
);
723+
operatorList.isOffscreenCanvasSupported =
724+
this.options.isOffscreenCanvasSupported;
720725
operatorList.addImageOps(
721726
OPS.paintInlineImageXObject,
722727
[imgData],
@@ -756,11 +761,22 @@ class PartialEvaluator {
756761
localColorSpaceCache,
757762
})
758763
.then(imageObj => {
759-
imgData = imageObj.createImageData(/* forceRGBA = */ false);
764+
imgData = imageObj.createImageData(
765+
/* forceRGBA = */ false,
766+
/* isOffscreenCanvasSupported = */ this.options
767+
.isOffscreenCanvasSupported
768+
);
760769

761770
if (cacheKey && imageRef && cacheGlobally) {
762-
this.globalImageCache.addByteSize(imageRef, imgData.data.length);
771+
let length = 0;
772+
if (imgData.bitmap) {
773+
length = imgData.width * imgData.height * 4;
774+
} else {
775+
length = imgData.data.length;
776+
}
777+
this.globalImageCache.addByteSize(imageRef, length);
763778
}
779+
764780
return this._sendImgData(objId, imgData, cacheGlobally);
765781
})
766782
.catch(reason => {

src/core/image.js

+141-32
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,18 @@
1313
* limitations under the License.
1414
*/
1515

16-
import { assert, FormatError, ImageKind, info, warn } from "../shared/util.js";
17-
import { applyMaskImageData } from "../shared/image_utils.js";
16+
import {
17+
assert,
18+
FeatureTest,
19+
FormatError,
20+
ImageKind,
21+
info,
22+
warn,
23+
} from "../shared/util.js";
24+
import {
25+
convertBlackAndWhiteToRGBA,
26+
convertToRGBA,
27+
} from "../shared/image_utils.js";
1828
import { BaseStream } from "./base_stream.js";
1929
import { ColorSpace } from "./colorspace.js";
2030
import { DecodeStream } from "./decode_stream.js";
@@ -364,11 +374,12 @@ class PDFImage {
364374
const canvas = new OffscreenCanvas(width, height);
365375
const ctx = canvas.getContext("2d");
366376
const imgData = ctx.createImageData(width, height);
367-
applyMaskImageData({
377+
convertBlackAndWhiteToRGBA({
368378
src: imgArray,
369379
dest: imgData.data,
370380
width,
371381
height,
382+
nonBlackColor: 0,
372383
inverseDecode,
373384
});
374385

@@ -641,7 +652,7 @@ class PDFImage {
641652
}
642653
}
643654

644-
createImageData(forceRGBA = false) {
655+
createImageData(forceRGBA = false, isOffscreenCanvasSupported = false) {
645656
const drawWidth = this.drawWidth;
646657
const drawHeight = this.drawHeight;
647658
const imgData = {
@@ -686,8 +697,12 @@ class PDFImage {
686697
drawWidth === originalWidth &&
687698
drawHeight === originalHeight
688699
) {
700+
const data = this.getImageBytes(originalHeight * rowBytes, {});
701+
if (isOffscreenCanvasSupported) {
702+
return this.createBitmap(kind, originalWidth, originalHeight, data);
703+
}
689704
imgData.kind = kind;
690-
imgData.data = this.getImageBytes(originalHeight * rowBytes, {});
705+
imgData.data = data;
691706

692707
if (this.needsDecode) {
693708
// Invert the buffer (which must be grayscale if we reached here).
@@ -704,21 +719,52 @@ class PDFImage {
704719
}
705720
if (this.image instanceof JpegStream && !this.smask && !this.mask) {
706721
let imageLength = originalHeight * rowBytes;
707-
switch (this.colorSpace.name) {
708-
case "DeviceGray":
709-
// Avoid truncating the image, since `JpegImage.getData`
710-
// will expand the image data when `forceRGB === true`.
711-
imageLength *= 3;
712-
/* falls through */
713-
case "DeviceRGB":
714-
case "DeviceCMYK":
715-
imgData.kind = ImageKind.RGB_24BPP;
716-
imgData.data = this.getImageBytes(imageLength, {
722+
if (isOffscreenCanvasSupported) {
723+
let isHandled = false;
724+
switch (this.colorSpace.name) {
725+
case "DeviceGray":
726+
// Avoid truncating the image, since `JpegImage.getData`
727+
// will expand the image data when `forceRGB === true`.
728+
imageLength *= 4;
729+
isHandled = true;
730+
break;
731+
case "DeviceRGB":
732+
imageLength = (imageLength / 3) * 4;
733+
isHandled = true;
734+
break;
735+
case "DeviceCMYK":
736+
isHandled = true;
737+
break;
738+
}
739+
740+
if (isHandled) {
741+
const rgba = this.getImageBytes(imageLength, {
717742
drawWidth,
718743
drawHeight,
719-
forceRGB: true,
744+
forceRGBA: true,
720745
});
721-
return imgData;
746+
return this.createBitmap(
747+
ImageKind.RGBA_32BPP,
748+
drawWidth,
749+
drawHeight,
750+
rgba
751+
);
752+
}
753+
} else {
754+
switch (this.colorSpace.name) {
755+
case "DeviceGray":
756+
imageLength *= 3;
757+
/* falls through */
758+
case "DeviceRGB":
759+
case "DeviceCMYK":
760+
imgData.kind = ImageKind.RGB_24BPP;
761+
imgData.data = this.getImageBytes(imageLength, {
762+
drawWidth,
763+
drawHeight,
764+
forceRGB: true,
765+
});
766+
return imgData;
767+
}
722768
}
723769
}
724770
}
@@ -735,32 +781,45 @@ class PDFImage {
735781
// If opacity data is present, use RGBA_32BPP form. Otherwise, use the
736782
// more compact RGB_24BPP form if allowable.
737783
let alpha01, maybeUndoPreblend;
784+
785+
let canvas, ctx, canvasImgData, data;
786+
if (isOffscreenCanvasSupported) {
787+
canvas = new OffscreenCanvas(drawWidth, drawHeight);
788+
ctx = canvas.getContext("2d");
789+
canvasImgData = ctx.createImageData(drawWidth, drawHeight);
790+
data = canvasImgData.data;
791+
}
792+
793+
imgData.kind = ImageKind.RGBA_32BPP;
794+
738795
if (!forceRGBA && !this.smask && !this.mask) {
739-
imgData.kind = ImageKind.RGB_24BPP;
740-
imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 3);
741-
alpha01 = 0;
796+
if (!isOffscreenCanvasSupported) {
797+
imgData.kind = ImageKind.RGB_24BPP;
798+
data = new Uint8ClampedArray(drawWidth * drawHeight * 3);
799+
alpha01 = 0;
800+
} else {
801+
const arr = new Uint32Array(data.buffer);
802+
arr.fill(FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff);
803+
alpha01 = 1;
804+
}
742805
maybeUndoPreblend = false;
743806
} else {
744-
imgData.kind = ImageKind.RGBA_32BPP;
745-
imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
807+
if (!isOffscreenCanvasSupported) {
808+
data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
809+
}
810+
746811
alpha01 = 1;
747812
maybeUndoPreblend = true;
748813

749814
// Color key masking (opacity) must be performed before decoding.
750-
this.fillOpacity(
751-
imgData.data,
752-
drawWidth,
753-
drawHeight,
754-
actualHeight,
755-
comps
756-
);
815+
this.fillOpacity(data, drawWidth, drawHeight, actualHeight, comps);
757816
}
758817

759818
if (this.needsDecode) {
760819
this.decodeBuffer(comps);
761820
}
762821
this.colorSpace.fillRgb(
763-
imgData.data,
822+
data,
764823
originalWidth,
765824
originalHeight,
766825
drawWidth,
@@ -771,9 +830,23 @@ class PDFImage {
771830
alpha01
772831
);
773832
if (maybeUndoPreblend) {
774-
this.undoPreblend(imgData.data, drawWidth, actualHeight);
833+
this.undoPreblend(data, drawWidth, actualHeight);
775834
}
776835

836+
if (isOffscreenCanvasSupported) {
837+
ctx.putImageData(canvasImgData, 0, 0);
838+
const bitmap = canvas.transferToImageBitmap();
839+
840+
return {
841+
data: null,
842+
width: drawWidth,
843+
height: drawHeight,
844+
bitmap,
845+
interpolate: this.interpolate,
846+
};
847+
}
848+
849+
imgData.data = data;
777850
return imgData;
778851
}
779852

@@ -833,13 +906,49 @@ class PDFImage {
833906
}
834907
}
835908

909+
createBitmap(kind, width, height, src) {
910+
const canvas = new OffscreenCanvas(width, height);
911+
const ctx = canvas.getContext("2d");
912+
let imgData;
913+
if (kind === ImageKind.RGBA_32BPP) {
914+
imgData = new ImageData(src, width, height);
915+
} else {
916+
imgData = ctx.createImageData(width, height);
917+
convertToRGBA({
918+
kind,
919+
src,
920+
dest: new Uint32Array(imgData.data.buffer),
921+
width,
922+
height,
923+
inverseDecode: this.needsDecode,
924+
});
925+
}
926+
ctx.putImageData(imgData, 0, 0);
927+
const bitmap = canvas.transferToImageBitmap();
928+
929+
return {
930+
data: null,
931+
width,
932+
height,
933+
bitmap,
934+
interpolate: this.interpolate,
935+
};
936+
}
937+
836938
getImageBytes(
837939
length,
838-
{ drawWidth, drawHeight, forceRGB = false, internal = false }
940+
{
941+
drawWidth,
942+
drawHeight,
943+
forceRGBA = false,
944+
forceRGB = false,
945+
internal = false,
946+
}
839947
) {
840948
this.image.reset();
841949
this.image.drawWidth = drawWidth || this.width;
842950
this.image.drawHeight = drawHeight || this.height;
951+
this.image.forceRGBA = !!forceRGBA;
843952
this.image.forceRGB = !!forceRGB;
844953
const imageBytes = this.image.getBytes(length);
845954

src/core/jpeg_stream.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class JpegStream extends DecodeStream {
6363

6464
// Checking if values need to be transformed before conversion.
6565
const decodeArr = this.dict.getArray("D", "Decode");
66-
if (this.forceRGB && Array.isArray(decodeArr)) {
66+
if ((this.forceRGBA || this.forceRGB) && Array.isArray(decodeArr)) {
6767
const bitsPerComponent = this.dict.get("BPC", "BitsPerComponent") || 8;
6868
const decodeArrLength = decodeArr.length;
6969
const transform = new Int32Array(decodeArrLength);
@@ -93,6 +93,7 @@ class JpegStream extends DecodeStream {
9393
const data = jpegImage.getData({
9494
width: this.drawWidth,
9595
height: this.drawHeight,
96+
forceRGBA: this.forceRGBA,
9697
forceRGB: this.forceRGB,
9798
isSourcePDF: true,
9899
});

0 commit comments

Comments
 (0)