SMask emulation
This commit is contained in:
parent
ac91047f6d
commit
4054b0c385
@ -96,7 +96,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
var groupOptions = {
|
var groupOptions = {
|
||||||
matrix: matrix,
|
matrix: matrix,
|
||||||
bbox: bbox,
|
bbox: bbox,
|
||||||
smask: !!smask,
|
smask: smask,
|
||||||
isolated: false,
|
isolated: false,
|
||||||
knockout: false
|
knockout: false
|
||||||
};
|
};
|
||||||
@ -105,8 +105,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
if (isName(groupSubtype) && groupSubtype.name === 'Transparency') {
|
if (isName(groupSubtype) && groupSubtype.name === 'Transparency') {
|
||||||
groupOptions.isolated = group.get('I') || false;
|
groupOptions.isolated = group.get('I') || false;
|
||||||
groupOptions.knockout = group.get('K') || false;
|
groupOptions.knockout = group.get('K') || false;
|
||||||
// There is also a group colorspace, but since we put everything in
|
var colorSpace = group.get('CS');
|
||||||
// RGB I'm not sure we need it.
|
groupOptions.colorSpace = colorSpace ?
|
||||||
|
ColorSpace.parseToIR(colorSpace, this.xref, resources) : null;
|
||||||
}
|
}
|
||||||
operatorList.addOp(OPS.beginGroup, [groupOptions]);
|
operatorList.addOp(OPS.beginGroup, [groupOptions]);
|
||||||
}
|
}
|
||||||
@ -196,6 +197,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
operatorList.addOp(OPS.paintImageXObject, args);
|
operatorList.addOp(OPS.paintImageXObject, args);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleSMask: function PartialEvaluator_handleSmask(smask, resources,
|
||||||
|
operatorList) {
|
||||||
|
var smaskContent = smask.get('G');
|
||||||
|
var smaskOptions = {
|
||||||
|
subtype: smask.get('S').name,
|
||||||
|
backdrop: smask.get('BC')
|
||||||
|
};
|
||||||
|
|
||||||
|
this.buildFormXObject(resources, smaskContent, smaskOptions,
|
||||||
|
operatorList);
|
||||||
|
},
|
||||||
|
|
||||||
handleTilingType: function PartialEvaluator_handleTilingType(
|
handleTilingType: function PartialEvaluator_handleTilingType(
|
||||||
fn, args, resources, pattern, patternDict,
|
fn, args, resources, pattern, patternDict,
|
||||||
operatorList) {
|
operatorList) {
|
||||||
@ -265,7 +278,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
setGState: function PartialEvaluator_setGState(resources, gState,
|
setGState: function PartialEvaluator_setGState(resources, gState,
|
||||||
operatorList) {
|
operatorList, xref) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
// TODO(mack): This should be rewritten so that this function returns
|
// TODO(mack): This should be rewritten so that this function returns
|
||||||
@ -295,9 +308,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
gStateObj.push([key, value]);
|
gStateObj.push([key, value]);
|
||||||
break;
|
break;
|
||||||
case 'SMask':
|
case 'SMask':
|
||||||
// We support the default so don't trigger a warning bar.
|
if (isName(value) && value.name === 'None') {
|
||||||
if (!isName(value) || value.name != 'None')
|
gStateObj.push([key, false]);
|
||||||
UnsupportedManager.notify(UNSUPPORTED_FEATURES.smask);
|
break;
|
||||||
|
}
|
||||||
|
var dict = xref.fetchIfRef(value);
|
||||||
|
if (isDict(dict)) {
|
||||||
|
self.handleSMask(dict, resources, operatorList);
|
||||||
|
gStateObj.push([key, true]);
|
||||||
|
} else {
|
||||||
|
warn('Unsupported SMask type');
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
// Only generate info log messages for the following since
|
// Only generate info log messages for the following since
|
||||||
// they are unlikey to have a big impact on the rendering.
|
// they are unlikey to have a big impact on the rendering.
|
||||||
@ -579,7 +601,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
var gState = extGState.get(dictName.name);
|
var gState = extGState.get(dictName.name);
|
||||||
self.setGState(resources, gState, operatorList);
|
self.setGState(resources, gState, operatorList, xref);
|
||||||
args = [];
|
args = [];
|
||||||
continue;
|
continue;
|
||||||
} // switch
|
} // switch
|
||||||
|
@ -376,6 +376,7 @@ var CanvasExtraState = (function CanvasExtraStateClosure() {
|
|||||||
this.fillAlpha = 1;
|
this.fillAlpha = 1;
|
||||||
this.strokeAlpha = 1;
|
this.strokeAlpha = 1;
|
||||||
this.lineWidth = 1;
|
this.lineWidth = 1;
|
||||||
|
this.activeSMask = null; // nonclonable field (see the save method below)
|
||||||
|
|
||||||
this.old = old;
|
this.old = old;
|
||||||
}
|
}
|
||||||
@ -416,6 +417,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
this.baseTransform = null;
|
this.baseTransform = null;
|
||||||
this.baseTransformStack = [];
|
this.baseTransformStack = [];
|
||||||
this.groupLevel = 0;
|
this.groupLevel = 0;
|
||||||
|
this.smaskStack = [];
|
||||||
|
this.smaskCounter = 0;
|
||||||
|
this.tempSMask = null;
|
||||||
if (canvasCtx) {
|
if (canvasCtx) {
|
||||||
addContextCurrentTransform(canvasCtx);
|
addContextCurrentTransform(canvasCtx);
|
||||||
}
|
}
|
||||||
@ -522,6 +526,74 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function composeSMask(ctx, smask, layerCtx) {
|
||||||
|
var mask = smask.canvas;
|
||||||
|
var maskCtx = smask.context;
|
||||||
|
var width = mask.width, height = mask.height;
|
||||||
|
|
||||||
|
var removeBackdropFn;
|
||||||
|
if (smask.backdrop) {
|
||||||
|
var cs = smask.colorSpace || ColorSpace.singletons.rgb;
|
||||||
|
var backdrop = cs.getRgb(smask.backdrop, 0);
|
||||||
|
removeBackdropFn = function (r0, g0, b0, layerDataBytes) {
|
||||||
|
var length = layerDataBytes.length;
|
||||||
|
for (var i = 3; i < length; i += 4) {
|
||||||
|
var alpha = layerDataBytes[i];
|
||||||
|
if (alpha !== 0 && alpha !== 255) {
|
||||||
|
var r = ((layerDataBytes[i - 3] * 255 -
|
||||||
|
r0 * (255 - alpha)) / alpha) | 0;
|
||||||
|
layerDataBytes[i - 3] = r < 0 ? 0 : r > 255 ? 255 : r;
|
||||||
|
var g = ((layerDataBytes[i - 2] * 255 -
|
||||||
|
g0 * (255 - alpha)) / alpha) | 0;
|
||||||
|
layerDataBytes[i - 2] = g < 0 ? 0 : g > 255 ? 255 : g;
|
||||||
|
var b = ((layerDataBytes[i - 1] * 255 -
|
||||||
|
b0 * (255 - alpha)) / alpha) | 0;
|
||||||
|
layerDataBytes[i - 1] = b < 0 ? 0 : b > 255 ? 255 : b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(null, backdrop[0], backdrop[1], backdrop[2]);
|
||||||
|
} else {
|
||||||
|
removeBackdropFn = function () {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var composeFn;
|
||||||
|
if (smask.subtype === 'Luminosity') {
|
||||||
|
composeFn = function (maskDataBytes, layerDataBytes) {
|
||||||
|
var length = maskDataBytes.length;
|
||||||
|
for (var i = 3; i < length; i += 4) {
|
||||||
|
var y = ((maskDataBytes[i - 3] * 77) + // * 0.3 / 255 * 0x10000
|
||||||
|
(maskDataBytes[i - 2] * 152) + // * 0.59 ....
|
||||||
|
(maskDataBytes[i - 1] * 28)) | 0; // * 0.11 ....
|
||||||
|
layerDataBytes[i] = (layerDataBytes[i] * y) >> 16;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
composeFn = function (maskDataBytes, layerDataBytes) {
|
||||||
|
var length = maskDataBytes.length;
|
||||||
|
for (var i = 3; i < length; i += 4) {
|
||||||
|
var alpha = maskDataBytes[i];
|
||||||
|
layerDataBytes[i] = (layerDataBytes[i] * alpha / 255) | 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// processing image in chunks to save memory
|
||||||
|
var chunkSize = 16;
|
||||||
|
for (var row = 0; row < height; row += chunkSize) {
|
||||||
|
var chunkHeight = Math.min(chunkSize, height - row);
|
||||||
|
var maskData = maskCtx.getImageData(0, row, width, chunkHeight);
|
||||||
|
var layerData = layerCtx.getImageData(0, row, width, chunkHeight);
|
||||||
|
|
||||||
|
removeBackdropFn(layerData.data);
|
||||||
|
composeFn(maskData.data, layerData.data);
|
||||||
|
|
||||||
|
maskCtx.putImageData(layerData, 0, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
ctx.drawImage(mask, smask.offsetX, smask.offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
var LINE_CAP_STYLES = ['butt', 'round', 'square'];
|
var LINE_CAP_STYLES = ['butt', 'round', 'square'];
|
||||||
var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
|
var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
|
||||||
var NORMAL_CLIP = {};
|
var NORMAL_CLIP = {};
|
||||||
@ -730,18 +802,70 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
this.ctx.globalCompositeOperation = 'source-over';
|
this.ctx.globalCompositeOperation = 'source-over';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'SMask':
|
||||||
|
if (this.current.activeSMask) {
|
||||||
|
this.endSMaskGroup();
|
||||||
|
}
|
||||||
|
this.current.activeSMask = value ? this.tempSMask : null;
|
||||||
|
if (this.current.activeSMask) {
|
||||||
|
this.beginSMaskGroup();
|
||||||
|
}
|
||||||
|
this.tempSMask = null;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beginSMaskGroup: function CanvasGraphics_beginSMaskGroup() {
|
||||||
|
|
||||||
|
var activeSMask = this.current.activeSMask;
|
||||||
|
var drawnWidth = activeSMask.canvas.width;
|
||||||
|
var drawnHeight = activeSMask.canvas.height;
|
||||||
|
var cacheId = 'smaskGroupAt' + this.groupLevel;
|
||||||
|
var scratchCanvas = CachedCanvases.getCanvas(
|
||||||
|
cacheId, drawnWidth, drawnHeight, true);
|
||||||
|
|
||||||
|
var currentCtx = this.ctx;
|
||||||
|
var currentTransform = currentCtx.mozCurrentTransform;
|
||||||
|
this.ctx.save();
|
||||||
|
|
||||||
|
var groupCtx = scratchCanvas.context;
|
||||||
|
groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY);
|
||||||
|
groupCtx.transform.apply(groupCtx, currentTransform);
|
||||||
|
|
||||||
|
copyCtxState(currentCtx, groupCtx);
|
||||||
|
this.ctx = groupCtx;
|
||||||
|
this.setGState([
|
||||||
|
['BM', 'Normal'],
|
||||||
|
['ca', 1],
|
||||||
|
['CA', 1]
|
||||||
|
]);
|
||||||
|
this.groupStack.push(currentCtx);
|
||||||
|
this.groupLevel++;
|
||||||
|
},
|
||||||
|
endSMaskGroup: function CanvasGraphics_endSMaskGroup() {
|
||||||
|
var groupCtx = this.ctx;
|
||||||
|
this.groupLevel--;
|
||||||
|
this.ctx = this.groupStack.pop();
|
||||||
|
|
||||||
|
composeSMask(this.ctx, this.current.activeSMask, groupCtx);
|
||||||
|
this.ctx.restore();
|
||||||
|
},
|
||||||
save: function CanvasGraphics_save() {
|
save: function CanvasGraphics_save() {
|
||||||
this.ctx.save();
|
this.ctx.save();
|
||||||
var old = this.current;
|
var old = this.current;
|
||||||
this.stateStack.push(old);
|
this.stateStack.push(old);
|
||||||
this.current = old.clone();
|
this.current = old.clone();
|
||||||
|
if (this.current.activeSMask) {
|
||||||
|
this.current.activeSMask = null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
restore: function CanvasGraphics_restore() {
|
restore: function CanvasGraphics_restore() {
|
||||||
var prev = this.stateStack.pop();
|
var prev = this.stateStack.pop();
|
||||||
if (prev) {
|
if (prev) {
|
||||||
|
if (this.current.activeSMask) {
|
||||||
|
this.endSMaskGroup();
|
||||||
|
}
|
||||||
|
|
||||||
this.current = prev;
|
this.current = prev;
|
||||||
this.ctx.restore();
|
this.ctx.restore();
|
||||||
}
|
}
|
||||||
@ -1571,9 +1695,15 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
var drawnWidth = Math.max(Math.ceil(bounds[2] - bounds[0]), 1);
|
var drawnWidth = Math.max(Math.ceil(bounds[2] - bounds[0]), 1);
|
||||||
var drawnHeight = Math.max(Math.ceil(bounds[3] - bounds[1]), 1);
|
var drawnHeight = Math.max(Math.ceil(bounds[3] - bounds[1]), 1);
|
||||||
|
|
||||||
|
var cacheId = 'groupAt' + this.groupLevel;
|
||||||
|
if (group.smask) {
|
||||||
|
// Using two cache entries is case if masks are used one after another.
|
||||||
|
cacheId += '_smask_' + ((this.smaskCounter++) % 2);
|
||||||
|
}
|
||||||
var scratchCanvas = CachedCanvases.getCanvas(
|
var scratchCanvas = CachedCanvases.getCanvas(
|
||||||
'groupAt' + this.groupLevel, drawnWidth, drawnHeight, true);
|
cacheId, drawnWidth, drawnHeight, true);
|
||||||
var groupCtx = scratchCanvas.context;
|
var groupCtx = scratchCanvas.context;
|
||||||
|
|
||||||
// Since we created a new canvas that is just the size of the bounding box
|
// Since we created a new canvas that is just the size of the bounding box
|
||||||
// we have to translate the group ctx.
|
// we have to translate the group ctx.
|
||||||
var offsetX = bounds[0];
|
var offsetX = bounds[0];
|
||||||
@ -1581,16 +1711,28 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
groupCtx.translate(-offsetX, -offsetY);
|
groupCtx.translate(-offsetX, -offsetY);
|
||||||
groupCtx.transform.apply(groupCtx, currentTransform);
|
groupCtx.transform.apply(groupCtx, currentTransform);
|
||||||
|
|
||||||
// Setup the current ctx so when the group is popped we draw it the right
|
if (group.smask) {
|
||||||
// location.
|
// Saving state and cached mask to be used in setGState.
|
||||||
currentCtx.setTransform(1, 0, 0, 1, 0, 0);
|
this.smaskStack.push({
|
||||||
currentCtx.translate(offsetX, offsetY);
|
canvas: scratchCanvas.canvas,
|
||||||
|
context: groupCtx,
|
||||||
|
offsetX: offsetX,
|
||||||
|
offsetY: offsetY,
|
||||||
|
subtype: group.smask.subtype,
|
||||||
|
backdrop: group.smask.backdrop,
|
||||||
|
colorSpace: group.colorSpace && ColorSpace.fromIR(group.colorSpace)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Setup the current ctx so when the group is popped we draw it at the
|
||||||
|
// right location.
|
||||||
|
currentCtx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
currentCtx.translate(offsetX, offsetY);
|
||||||
|
}
|
||||||
// The transparency group inherits all off the current graphics state
|
// The transparency group inherits all off the current graphics state
|
||||||
// except the blend mode, soft mask, and alpha constants.
|
// except the blend mode, soft mask, and alpha constants.
|
||||||
copyCtxState(currentCtx, groupCtx);
|
copyCtxState(currentCtx, groupCtx);
|
||||||
this.ctx = groupCtx;
|
this.ctx = groupCtx;
|
||||||
this.setGState([
|
this.setGState([
|
||||||
['SMask', 'None'],
|
|
||||||
['BM', 'Normal'],
|
['BM', 'Normal'],
|
||||||
['ca', 1],
|
['ca', 1],
|
||||||
['CA', 1]
|
['CA', 1]
|
||||||
@ -1610,7 +1752,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
} else {
|
} else {
|
||||||
this.ctx.mozImageSmoothingEnabled = false;
|
this.ctx.mozImageSmoothingEnabled = false;
|
||||||
}
|
}
|
||||||
this.ctx.drawImage(groupCtx.canvas, 0, 0);
|
if (group.smask) {
|
||||||
|
this.tempSMask = this.smaskStack.pop();
|
||||||
|
} else {
|
||||||
|
this.ctx.drawImage(groupCtx.canvas, 0, 0);
|
||||||
|
}
|
||||||
this.restore();
|
this.restore();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user