mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-26 10:08:06 +02:00
Improve performance with image masks (bug 857031)
- it aims to partially fix performance issue reported: https://bugzilla.mozilla.org/show_bug.cgi?id=857031; - the idea is too avoid to use byte arrays but use ImageBitmap which are a way faster to draw: * an ImageBitmap is Transferable which means that it can be built in the worker instead of in the main thread: - this is achieved in using an OffscreenCanvas when it's available, there is a bug to enable them for pdf.js: https://bugzilla.mozilla.org/show_bug.cgi?id=1763330; - or in using createImageBitmap: in Firefox a task is sent to the main thread to build the bitmap so it's slightly slower than using an OffscreenCanvas. * it's transfered from the worker to the main thread by "reference"; * the byte buffers used to create the image data have a very short lifetime and ergo the memory used is globally less than before. - Use the localImageCache for the mask; - Fix the pdf issue4436r.pdf: it was expected to have a binary stream for the image; - Move the singlePixel trick from operator_list to image: this way we can use this trick even if it isn't in a set as defined in operator_list.
This commit is contained in:
parent
2b673a6941
commit
040fcae5ab
11 changed files with 256 additions and 65 deletions
|
@ -540,7 +540,7 @@ class PartialEvaluator {
|
|||
}
|
||||
|
||||
_sendImgData(objId, imgData, cacheGlobally = false) {
|
||||
const transfers = imgData ? [imgData.data.buffer] : null;
|
||||
const transfers = imgData ? [imgData.bitmap || imgData.data.buffer] : null;
|
||||
|
||||
if (this.parsingType3Font || cacheGlobally) {
|
||||
return this.handler.send(
|
||||
|
@ -612,6 +612,33 @@ class PartialEvaluator {
|
|||
);
|
||||
const decode = dict.getArray("D", "Decode");
|
||||
|
||||
if (this.parsingType3Font) {
|
||||
imgData = PDFImage.createRawMask({
|
||||
imgArray,
|
||||
width: w,
|
||||
height: h,
|
||||
imageIsFromDecodeStream: image instanceof DecodeStream,
|
||||
inverseDecode: !!decode && decode[0] > 0,
|
||||
interpolate,
|
||||
});
|
||||
|
||||
imgData.cached = !!cacheKey;
|
||||
args = [imgData];
|
||||
|
||||
operatorList.addOp(OPS.paintImageMaskXObject, args);
|
||||
if (cacheKey) {
|
||||
localImageCache.set(cacheKey, imageRef, {
|
||||
fn: OPS.paintImageMaskXObject,
|
||||
args,
|
||||
});
|
||||
}
|
||||
|
||||
if (optionalContent !== undefined) {
|
||||
operatorList.addOp(OPS.endMarkedContent, []);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
imgData = PDFImage.createMask({
|
||||
imgArray,
|
||||
width: w,
|
||||
|
@ -620,8 +647,36 @@ class PartialEvaluator {
|
|||
inverseDecode: !!decode && decode[0] > 0,
|
||||
interpolate,
|
||||
});
|
||||
imgData.cached = !!cacheKey;
|
||||
args = [imgData];
|
||||
|
||||
if (imgData.isSingleOpaquePixel) {
|
||||
// Handles special case of mainly LaTeX documents which use image
|
||||
// masks to draw lines with the current fill style.
|
||||
operatorList.addOp(OPS.paintSolidColorImageMask, []);
|
||||
if (cacheKey) {
|
||||
localImageCache.set(cacheKey, imageRef, {
|
||||
fn: OPS.paintSolidColorImageMask,
|
||||
args: [],
|
||||
});
|
||||
}
|
||||
|
||||
if (optionalContent !== undefined) {
|
||||
operatorList.addOp(OPS.endMarkedContent, []);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const objId = `mask_${this.idFactory.createObjId()}`;
|
||||
operatorList.addDependency(objId);
|
||||
this._sendImgData(objId, imgData);
|
||||
|
||||
args = [
|
||||
{
|
||||
data: objId,
|
||||
width: imgData.width,
|
||||
height: imgData.height,
|
||||
interpolate: imgData.interpolate,
|
||||
},
|
||||
];
|
||||
|
||||
operatorList.addOp(OPS.paintImageMaskXObject, args);
|
||||
if (cacheKey) {
|
||||
|
|
|
@ -13,7 +13,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert, FormatError, ImageKind, info, warn } from "../shared/util.js";
|
||||
import {
|
||||
assert,
|
||||
FeatureTest,
|
||||
FormatError,
|
||||
ImageKind,
|
||||
info,
|
||||
warn,
|
||||
} from "../shared/util.js";
|
||||
import { applyMaskImageData } from "../shared/image_utils.js";
|
||||
import { BaseStream } from "./base_stream.js";
|
||||
import { ColorSpace } from "./colorspace.js";
|
||||
import { DecodeStream } from "./decode_stream.js";
|
||||
|
@ -288,7 +296,7 @@ class PDFImage {
|
|||
});
|
||||
}
|
||||
|
||||
static createMask({
|
||||
static createRawMask({
|
||||
imgArray,
|
||||
width,
|
||||
height,
|
||||
|
@ -302,7 +310,7 @@ class PDFImage {
|
|||
) {
|
||||
assert(
|
||||
imgArray instanceof Uint8ClampedArray,
|
||||
'PDFImage.createMask: Unsupported "imgArray" type.'
|
||||
'PDFImage.createRawMask: Unsupported "imgArray" type.'
|
||||
);
|
||||
}
|
||||
// |imgArray| might not contain full data for every pixel of the mask, so
|
||||
|
@ -343,6 +351,69 @@ class PDFImage {
|
|||
return { data, width, height, interpolate };
|
||||
}
|
||||
|
||||
static createMask({
|
||||
imgArray,
|
||||
width,
|
||||
height,
|
||||
imageIsFromDecodeStream,
|
||||
inverseDecode,
|
||||
interpolate,
|
||||
}) {
|
||||
if (
|
||||
typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || TESTING")
|
||||
) {
|
||||
assert(
|
||||
imgArray instanceof Uint8ClampedArray,
|
||||
'PDFImage.createMask: Unsupported "imgArray" type.'
|
||||
);
|
||||
}
|
||||
|
||||
const isSingleOpaquePixel =
|
||||
width === 1 &&
|
||||
height === 1 &&
|
||||
inverseDecode === (imgArray.length === 0 || !!(imgArray[0] & 128));
|
||||
|
||||
if (isSingleOpaquePixel) {
|
||||
return { isSingleOpaquePixel };
|
||||
}
|
||||
|
||||
if (FeatureTest.isOffscreenCanvasSupported) {
|
||||
const canvas = new OffscreenCanvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
const imgData = ctx.createImageData(width, height);
|
||||
applyMaskImageData({
|
||||
src: imgArray,
|
||||
dest: imgData.data,
|
||||
width,
|
||||
height,
|
||||
inverseDecode,
|
||||
});
|
||||
|
||||
ctx.putImageData(imgData, 0, 0);
|
||||
const bitmap = canvas.transferToImageBitmap();
|
||||
|
||||
return {
|
||||
data: null,
|
||||
width,
|
||||
height,
|
||||
interpolate,
|
||||
bitmap,
|
||||
};
|
||||
}
|
||||
|
||||
// Get the data almost as they're and they'll be decoded
|
||||
// just before being drawn.
|
||||
return this.createRawMask({
|
||||
imgArray,
|
||||
width,
|
||||
height,
|
||||
inverseDecode,
|
||||
imageIsFromDecodeStream,
|
||||
interpolate,
|
||||
});
|
||||
}
|
||||
|
||||
get drawWidth() {
|
||||
return Math.max(
|
||||
this.width,
|
||||
|
|
|
@ -35,31 +35,6 @@ function addState(parentState, pattern, checkFn, iterateFn, processFn) {
|
|||
};
|
||||
}
|
||||
|
||||
function handlePaintSolidColorImageMask(iFirstSave, count, fnArray, argsArray) {
|
||||
// Handles special case of mainly LaTeX documents which use image masks to
|
||||
// draw lines with the current fill style.
|
||||
// 'count' groups of (save, transform, paintImageMaskXObject, restore)+
|
||||
// have been found at iFirstSave.
|
||||
const iFirstPIMXO = iFirstSave + 2;
|
||||
let i;
|
||||
for (i = 0; i < count; i++) {
|
||||
const arg = argsArray[iFirstPIMXO + 4 * i];
|
||||
const imageMask = arg.length === 1 && arg[0];
|
||||
if (
|
||||
imageMask &&
|
||||
imageMask.width === 1 &&
|
||||
imageMask.height === 1 &&
|
||||
(!imageMask.data.length ||
|
||||
(imageMask.data.length === 1 && imageMask.data[0] === 0))
|
||||
) {
|
||||
fnArray[iFirstPIMXO + 4 * i] = OPS.paintSolidColorImageMask;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return count - i;
|
||||
}
|
||||
|
||||
const InitialState = [];
|
||||
|
||||
// This replaces (save, transform, paintInlineImageXObject, restore)+
|
||||
|
@ -216,12 +191,6 @@ addState(
|
|||
// At this point, i is the index of the first op past the last valid
|
||||
// quartet.
|
||||
let count = Math.floor((i - iFirstSave) / 4);
|
||||
count = handlePaintSolidColorImageMask(
|
||||
iFirstSave,
|
||||
count,
|
||||
fnArray,
|
||||
argsArray
|
||||
);
|
||||
if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
|
||||
return i - ((i - iFirstSave) % 4);
|
||||
}
|
||||
|
@ -701,11 +670,16 @@ class OperatorList {
|
|||
PDFJSDev.test("!PRODUCTION || TESTING")
|
||||
) {
|
||||
assert(
|
||||
arg.data instanceof Uint8ClampedArray,
|
||||
arg.data instanceof Uint8ClampedArray ||
|
||||
typeof arg.data === "string",
|
||||
'OperatorList._transfers: Unsupported "arg.data" type.'
|
||||
);
|
||||
}
|
||||
if (!arg.cached) {
|
||||
if (
|
||||
!arg.cached &&
|
||||
arg.data &&
|
||||
arg.data.buffer instanceof ArrayBuffer
|
||||
) {
|
||||
transfers.push(arg.data.buffer);
|
||||
}
|
||||
break;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue