Caching inlined mask images
This commit is contained in:
parent
f48f57e30a
commit
257898b359
@ -152,10 +152,16 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
var canTransfer = image instanceof DecodeStream;
|
||||
var inverseDecode = !!decode && decode[0] > 0;
|
||||
|
||||
operatorList.addOp(OPS.paintImageMaskXObject,
|
||||
[PDFImage.createMask(imgArray, width, height, canTransfer,
|
||||
inverseDecode)]
|
||||
);
|
||||
var imgData = PDFImage.createMask(imgArray, width, height,
|
||||
canTransfer, inverseDecode);
|
||||
imgData.cached = true;
|
||||
var args = [imgData];
|
||||
operatorList.addOp(OPS.paintImageMaskXObject, args);
|
||||
if (cacheKey) {
|
||||
cache.key = cacheKey;
|
||||
cache.fn = OPS.paintImageMaskXObject;
|
||||
cache.args = args;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -553,8 +559,14 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
args[0] = loadedName;
|
||||
break;
|
||||
case OPS.endInlineImage:
|
||||
var cacheKey = args[0].cacheKey;
|
||||
if (cacheKey && imageCache.key === cacheKey) {
|
||||
operatorList.addOp(imageCache.fn, imageCache.args);
|
||||
args = [];
|
||||
continue;
|
||||
}
|
||||
self.buildPaintImageXObject(resources, args[0], true,
|
||||
operatorList);
|
||||
operatorList, cacheKey, imageCache);
|
||||
args = [];
|
||||
continue;
|
||||
case OPS.save:
|
||||
@ -1276,7 +1288,9 @@ var OperatorList = (function OperatorListClosure() {
|
||||
case OPS.paintInlineImageXObjectGroup:
|
||||
case OPS.paintImageMaskXObject:
|
||||
var arg = argsArray[i][0]; // first param in imgData
|
||||
transfers.push(arg.data.buffer);
|
||||
if (!arg.cached) {
|
||||
transfers.push(arg.data.buffer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1774,6 +1788,7 @@ var QueueOptimizer = (function QueueOptimizerClosure() {
|
||||
// searching for (save, transform, paintImageMaskXObject, restore)+
|
||||
var MIN_IMAGES_IN_MASKS_BLOCK = 10;
|
||||
var MAX_IMAGES_IN_MASKS_BLOCK = 100;
|
||||
var MAX_SAME_IMAGES_IN_MASKS_BLOCK = 1000;
|
||||
|
||||
var fnArray = context.fnArray, argsArray = context.argsArray;
|
||||
var j = context.currentOperation - 3, i = j + 4;
|
||||
@ -1781,24 +1796,69 @@ var QueueOptimizer = (function QueueOptimizerClosure() {
|
||||
|
||||
for (; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
|
||||
}
|
||||
var count = Math.min((i - j) >> 2, MAX_IMAGES_IN_MASKS_BLOCK);
|
||||
var count = (i - j) >> 2;
|
||||
if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
|
||||
context.currentOperation = i - 1;
|
||||
return;
|
||||
}
|
||||
var images = [];
|
||||
for (var q = 0; q < count; q++) {
|
||||
var transform = argsArray[j + (q << 2) + 1];
|
||||
var maskParams = argsArray[j + (q << 2) + 2][0];
|
||||
images.push({data: maskParams.data, width: maskParams.width,
|
||||
height: maskParams.height, transform: transform});
|
||||
}
|
||||
// replacing queue items
|
||||
squash(fnArray, j, count * 4, OPS.paintImageMaskXObjectGroup);
|
||||
argsArray.splice(j, count * 4, [images]);
|
||||
|
||||
context.currentOperation = j;
|
||||
context.operationsLength -= count * 4 - 1;
|
||||
var isSameImage = false;
|
||||
if (argsArray[j + 1][1] === 0 && argsArray[j + 1][2] === 0) {
|
||||
i = j + 4;
|
||||
isSameImage = true;
|
||||
for (var q = 1; q < count; q++, i += 4) {
|
||||
var prevTransformArgs = argsArray[i - 3];
|
||||
var transformArgs = argsArray[i + 1];
|
||||
if (argsArray[i - 2][0] !== argsArray[i + 2][0] ||
|
||||
prevTransformArgs[0] !== transformArgs[0] ||
|
||||
prevTransformArgs[1] !== transformArgs[1] ||
|
||||
prevTransformArgs[2] !== transformArgs[2] ||
|
||||
prevTransformArgs[3] !== transformArgs[3]) {
|
||||
if (q < MIN_IMAGES_IN_MASKS_BLOCK) {
|
||||
isSameImage = false;
|
||||
} else {
|
||||
count = q;
|
||||
}
|
||||
break; // different image or transform
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSameImage) {
|
||||
count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK);
|
||||
var positions = new Float32Array(count * 2);
|
||||
i = j + 1;
|
||||
for (var q = 0; q < count; q++) {
|
||||
var transformArgs = argsArray[i];
|
||||
positions[(q << 1)] = transformArgs[4];
|
||||
positions[(q << 1) + 1] = transformArgs[5];
|
||||
i += 4;
|
||||
}
|
||||
|
||||
// replacing queue items
|
||||
squash(fnArray, j, count * 4, OPS.paintImageMaskXObjectRepeat);
|
||||
argsArray.splice(j, count * 4, [argsArray[j + 2][0],
|
||||
argsArray[j + 1][0], argsArray[j + 1][3], positions]);
|
||||
|
||||
context.currentOperation = j;
|
||||
context.operationsLength -= count * 4 - 1;
|
||||
} else {
|
||||
count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK);
|
||||
var images = [];
|
||||
for (var q = 0; q < count; q++) {
|
||||
var transformArgs = argsArray[j + (q << 2) + 1];
|
||||
var maskParams = argsArray[j + (q << 2) + 2][0];
|
||||
images.push({data: maskParams.data, width: maskParams.width,
|
||||
height: maskParams.height, transform: transformArgs});
|
||||
}
|
||||
|
||||
// replacing queue items
|
||||
squash(fnArray, j, count * 4, OPS.paintImageMaskXObjectGroup);
|
||||
argsArray.splice(j, count * 4, [images]);
|
||||
|
||||
context.currentOperation = j;
|
||||
context.operationsLength -= count * 4 - 1;
|
||||
}
|
||||
});
|
||||
|
||||
addState(InitialState,
|
||||
@ -1848,7 +1908,7 @@ var QueueOptimizer = (function QueueOptimizerClosure() {
|
||||
var args = [argsArray[j + 2][0], argsArray[j + 1][0],
|
||||
argsArray[j + 1][3], positions];
|
||||
// replacing queue items
|
||||
squash(fnArray, j, count * 4, OPS.paintImageMaskXObjectRepeat);
|
||||
squash(fnArray, j, count * 4, OPS.paintImageXObjectRepeat);
|
||||
argsArray.splice(j, count * 4, args);
|
||||
|
||||
context.currentOperation = j;
|
||||
|
@ -32,6 +32,11 @@ var Parser = (function ParserClosure() {
|
||||
this.lexer = lexer;
|
||||
this.allowStreams = allowStreams;
|
||||
this.xref = xref;
|
||||
this.imageCache = {
|
||||
length: 0,
|
||||
adler32: 0,
|
||||
stream: null
|
||||
};
|
||||
this.refill();
|
||||
}
|
||||
|
||||
@ -169,10 +174,48 @@ var Parser = (function ParserClosure() {
|
||||
|
||||
var length = (stream.pos - 4) - startPos;
|
||||
var imageStream = stream.makeSubStream(startPos, length, dict);
|
||||
if (cipherTransform)
|
||||
|
||||
// trying to cache repeat images, first we are trying to "warm up" caching
|
||||
// using length, then comparing adler32
|
||||
var MAX_LENGTH_TO_CACHE = 1000;
|
||||
var cacheImage = false, adler32;
|
||||
if (length < MAX_LENGTH_TO_CACHE && this.imageCache.length === length) {
|
||||
var imageBytes = imageStream.getBytes();
|
||||
imageStream.reset();
|
||||
|
||||
var a = 1;
|
||||
var b = 0;
|
||||
for (var i = 0, ii = imageBytes.length; i < ii; ++i) {
|
||||
a = (a + (imageBytes[i] & 0xff)) % 65521;
|
||||
b = (b + a) % 65521;
|
||||
}
|
||||
adler32 = (b << 16) | a;
|
||||
|
||||
if (this.imageCache.stream && this.imageCache.adler32 === adler32) {
|
||||
this.buf2 = Cmd.get('EI');
|
||||
this.shift();
|
||||
|
||||
this.imageCache.stream.reset();
|
||||
return this.imageCache.stream;
|
||||
}
|
||||
cacheImage = true;
|
||||
}
|
||||
if (!cacheImage && !this.imageCache.stream) {
|
||||
this.imageCache.length = length;
|
||||
this.imageCache.stream = null;
|
||||
}
|
||||
|
||||
if (cipherTransform) {
|
||||
imageStream = cipherTransform.createStream(imageStream, length);
|
||||
}
|
||||
|
||||
imageStream = this.filter(imageStream, dict, length);
|
||||
imageStream.dict = dict;
|
||||
if (cacheImage) {
|
||||
imageStream.cacheKey = 'inline_' + length + '_' + adler32;
|
||||
this.imageCache.adler32 = adler32;
|
||||
this.imageCache.stream = imageStream;
|
||||
}
|
||||
|
||||
this.buf2 = Cmd.get('EI');
|
||||
this.shift();
|
||||
|
@ -1924,6 +1924,39 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
||||
this.paintInlineImageXObject(maskCanvas.canvas);
|
||||
},
|
||||
|
||||
paintImageMaskXObjectRepeat:
|
||||
function CanvasGraphics_paintImageMaskXObjectRepeat(imgData, scaleX,
|
||||
scaleY, positions) {
|
||||
var width = imgData.width;
|
||||
var height = imgData.height;
|
||||
var ctx = this.ctx;
|
||||
|
||||
var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
|
||||
var maskCtx = maskCanvas.context;
|
||||
maskCtx.save();
|
||||
|
||||
putBinaryImageMask(maskCtx, imgData);
|
||||
|
||||
maskCtx.globalCompositeOperation = 'source-in';
|
||||
|
||||
var fillColor = this.current.fillColor;
|
||||
maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
|
||||
fillColor.type === 'Pattern') ?
|
||||
fillColor.getPattern(maskCtx, this) : fillColor;
|
||||
maskCtx.fillRect(0, 0, width, height);
|
||||
|
||||
maskCtx.restore();
|
||||
|
||||
for (var i = 0, ii = positions.length; i < ii; i += 2) {
|
||||
ctx.save();
|
||||
ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]);
|
||||
ctx.scale(1, -1);
|
||||
ctx.drawImage(maskCanvas.canvas, 0, 0, width, height,
|
||||
0, -1, 1, 1);
|
||||
ctx.restore();
|
||||
}
|
||||
},
|
||||
|
||||
paintImageMaskXObjectGroup:
|
||||
function CanvasGraphics_paintImageMaskXObjectGroup(images) {
|
||||
var ctx = this.ctx;
|
||||
@ -1966,22 +1999,22 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
||||
this.paintInlineImageXObject(imgData);
|
||||
},
|
||||
|
||||
paintImageMaskXObjectRepeat:
|
||||
function CanvasGraphics_paintImageMaskXObjectRepeat(objId, scaleX, scaleY,
|
||||
paintImageXObjectRepeat:
|
||||
function CanvasGraphics_paintImageXObjectRepeat(objId, scaleX, scaleY,
|
||||
positions) {
|
||||
var imgData = this.objs.get(objId);
|
||||
if (!imgData) {
|
||||
error('Dependent image isn\'t ready yet');
|
||||
}
|
||||
var imgData = this.objs.get(objId);
|
||||
if (!imgData) {
|
||||
error('Dependent image isn\'t ready yet');
|
||||
}
|
||||
|
||||
var width = imgData.width;
|
||||
var height = imgData.height;
|
||||
var map = [];
|
||||
for (var i = 0, ii = positions.length; i < ii; i += 2) {
|
||||
map.push({transform: [scaleX, 0, 0, scaleY, positions[i],
|
||||
positions[i + 1]], x: 0, y: 0, w: width, h: height});
|
||||
}
|
||||
this.paintInlineImageXObjectGroup(imgData, map);
|
||||
var width = imgData.width;
|
||||
var height = imgData.height;
|
||||
var map = [];
|
||||
for (var i = 0, ii = positions.length; i < ii; i += 2) {
|
||||
map.push({transform: [scaleX, 0, 0, scaleY, positions[i],
|
||||
positions[i + 1]], x: 0, y: 0, w: width, h: height});
|
||||
}
|
||||
this.paintInlineImageXObjectGroup(imgData, map);
|
||||
},
|
||||
|
||||
paintInlineImageXObject:
|
||||
|
@ -150,7 +150,8 @@ var OPS = PDFJS.OPS = {
|
||||
paintImageXObject: 85,
|
||||
paintInlineImageXObject: 86,
|
||||
paintInlineImageXObjectGroup: 87,
|
||||
paintImageMaskXObjectRepeat: 88
|
||||
paintImageXObjectRepeat: 88,
|
||||
paintImageMaskXObjectRepeat: 89
|
||||
};
|
||||
|
||||
// A notice for devs. These are good for things that are helpful to devs, such
|
||||
|
Loading…
x
Reference in New Issue
Block a user