Merge pull request #3312 from yurydelendik/no-getimagedata

Removes getImageData from canvas.js
This commit is contained in:
Brendan Dahl 2013-06-03 10:44:56 -07:00
commit 2e130903ef
4 changed files with 134 additions and 209 deletions

View File

@ -167,6 +167,29 @@ function addContextCurrentTransform(ctx) {
}
}
var CachedCanvases = (function CachedCanvasesClosure() {
var cache = {};
return {
getCanvas: function CachedCanvases_getCanvas(id, width, height) {
var canvas;
if (id in cache) {
canvas = cache[id];
canvas.width = width;
canvas.height = height;
// reset canvas transform for emulated mozCurrentTransform, if needed
canvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0);
} else {
canvas = createScratchCanvas(width, height);
cache[id] = canvas;
}
return canvas;
},
clear: function () {
cache = {};
}
};
})();
function compileType3Glyph(imgData) {
var POINT_TO_PROCESS_LIMIT = 1000;
@ -394,31 +417,15 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}
}
function applyStencilMask(imgArray, width, height, inverseDecode, buffer) {
var imgArrayPos = 0;
var i, j, mask, buf;
// removing making non-masked pixels transparent
var bufferPos = 3; // alpha component offset
for (i = 0; i < height; i++) {
mask = 0;
for (j = 0; j < width; j++) {
if (!mask) {
buf = imgArray[imgArrayPos++];
mask = 128;
}
if (!(buf & mask) === inverseDecode) {
buffer[bufferPos] = 0;
}
bufferPos += 4;
mask >>= 1;
}
function putBinaryImageData(ctx, imgData) {
if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
ctx.putImageData(imgData, 0, 0);
return;
}
}
function putBinaryImageData(ctx, data, w, h) {
var tmpImgData = 'createImageData' in ctx ? ctx.createImageData(w, h) :
ctx.getImageData(0, 0, w, h);
var tmpImgData = ctx.createImageData(imgData.width, imgData.height);
var data = imgData.data;
var tmpImgDataPixels = tmpImgData.data;
if ('set' in tmpImgDataPixels)
tmpImgDataPixels.set(data);
@ -431,96 +438,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
ctx.putImageData(tmpImgData, 0, 0);
}
function prescaleImage(pixels, width, height, widthScale, heightScale) {
pixels = new Uint8Array(pixels); // creating a copy
while (widthScale > 2 || heightScale > 2) {
if (heightScale > 2) {
// scaling image twice vertically
var rowSize = width * 4;
var k = 0, l = 0;
for (var i = 0; i < height - 1; i += 2) {
for (var j = 0; j < width; j++) {
var alpha1 = pixels[k + 3], alpha2 = pixels[k + 3 + rowSize];
if (alpha1 === alpha2) {
pixels[l] = (pixels[k] + pixels[k + rowSize]) >> 1;
pixels[l + 1] = (pixels[k + 1] + pixels[k + 1 + rowSize]) >> 1;
pixels[l + 2] = (pixels[k + 2] + pixels[k + 2 + rowSize]) >> 1;
pixels[l + 3] = alpha1;
} else if (alpha1 < alpha2) {
var d = 256 - alpha2 + alpha1;
pixels[l] = (pixels[k] * d + (pixels[k + rowSize] << 8)) >> 9;
pixels[l + 1] = (pixels[k + 1] * d +
(pixels[k + 1 + rowSize] << 8)) >> 9;
pixels[l + 2] = (pixels[k + 2] * d +
(pixels[k + 2 + rowSize] << 8)) >> 9;
pixels[l + 3] = alpha2;
} else {
var d = 256 - alpha1 + alpha2;
pixels[l] = ((pixels[k] << 8) + pixels[k + rowSize] * d) >> 9;
pixels[l + 1] = ((pixels[k + 1] << 8) +
pixels[k + 1 + rowSize] * d) >> 9;
pixels[l + 2] = ((pixels[k + 2] << 8) +
pixels[k + 2 + rowSize] * d) >> 9;
pixels[l + 3] = alpha1;
}
k += 4; l += 4;
}
k += rowSize;
}
if (height & 1) {
for (var i = 0; i < rowSize; i++) {
pixels[l++] = pixels[k++];
}
}
height = (height + 1) >> 1;
heightScale /= 2;
}
if (widthScale > 2) {
// scaling image twice horizontally
var k = 0, l = 0;
for (var i = 0; i < height; i++) {
for (var j = 0; j < width - 1; j += 2) {
var alpha1 = pixels[k + 3], alpha2 = pixels[k + 7];
if (alpha1 === alpha2) {
pixels[l] = (pixels[k] + pixels[k + 4]) >> 1;
pixels[l + 1] = (pixels[k + 1] + pixels[k + 5]) >> 1;
pixels[l + 2] = (pixels[k + 2] + pixels[k + 6]) >> 1;
pixels[l + 3] = alpha1;
} else if (alpha1 < alpha2) {
var d = 256 - alpha2 + alpha1;
pixels[l] = (pixels[k] * d + (pixels[k + 4] << 8)) >> 9;
pixels[l + 1] = (pixels[k + 1] * d + (pixels[k + 5] << 8)) >> 9;
pixels[l + 2] = (pixels[k + 2] * d + (pixels[k + 6] << 8)) >> 9;
pixels[l + 3] = alpha2;
} else {
var d = 256 - alpha1 + alpha2;
pixels[l] = ((pixels[k] << 8) + pixels[k + 4] * d) >> 9;
pixels[l + 1] = ((pixels[k + 1] << 8) + pixels[k + 5] * d) >> 9;
pixels[l + 2] = ((pixels[k + 2] << 8) + pixels[k + 6] * d) >> 9;
pixels[l + 3] = alpha1;
}
k += 8; l += 4;
}
if (width & 1) {
pixels[l++] = pixels[k++];
pixels[l++] = pixels[k++];
pixels[l++] = pixels[k++];
pixels[l++] = pixels[k++];
}
}
width = (width + 1) >> 1;
widthScale /= 2;
}
}
var tmpCanvas = createScratchCanvas(width, height);
var tmpCtx = tmpCanvas.getContext('2d');
putBinaryImageData(tmpCtx, pixels.subarray(0, width * height * 4),
width, height);
return tmpCanvas;
}
function copyCtxState(sourceCtx, destCtx) {
var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha',
'lineWidth', 'lineCap', 'lineJoin', 'miterLimit',
@ -681,6 +598,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
endDrawing: function CanvasGraphics_endDrawing() {
this.ctx.restore();
CachedCanvases.clear();
if (this.textLayer) {
this.textLayer.endLayout();
@ -1704,21 +1622,17 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.restore();
},
paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(
imgArray, inverseDecode, width, height) {
paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
var ctx = this.ctx;
var width = img.width, height = img.height;
var glyph = this.processingType3;
if (COMPILE_TYPE3_GLYPHS && glyph && !('compiled' in glyph)) {
var MAX_SIZE_TO_COMPILE = 1000;
if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
var pixels = new Uint8Array(width * height * 4);
for (var i = 3, ii = pixels.length; i < ii; i += 4) {
pixels[i] = 255;
}
applyStencilMask(imgArray, width, height, inverseDecode, pixels);
glyph.compiled =
compileType3Glyph({data: pixels, width: width, height: height});
compileType3Glyph({data: img.data, width: width, height: height});
} else {
glyph.compiled = null;
}
@ -1729,55 +1643,53 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
return;
}
var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
var maskCtx = maskCanvas.getContext('2d');
maskCtx.save();
var tmpCanvas = createScratchCanvas(width, height);
var tmpCtx = tmpCanvas.getContext('2d');
putBinaryImageData(maskCtx, img);
maskCtx.globalCompositeOperation = 'source-in';
var fillColor = this.current.fillColor;
tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
fillColor.type === 'Pattern') ?
fillColor.getPattern(tmpCtx) : fillColor;
tmpCtx.fillRect(0, 0, width, height);
fillColor.getPattern(maskCtx) : fillColor;
maskCtx.fillRect(0, 0, width, height);
var imgData = tmpCtx.getImageData(0, 0, width, height);
var pixels = imgData.data;
maskCtx.restore();
applyStencilMask(imgArray, width, height, inverseDecode, pixels);
this.paintInlineImageXObject(imgData);
this.paintInlineImageXObject(maskCanvas);
},
paintImageMaskXObjectGroup:
function CanvasGraphics_paintImageMaskXObjectGroup(images) {
var ctx = this.ctx;
var tmpCanvasWidth = 0, tmpCanvasHeight = 0, tmpCanvas, tmpCtx;
for (var i = 0, ii = images.length; i < ii; i++) {
var image = images[i];
var w = image.width, h = image.height;
if (w > tmpCanvasWidth || h > tmpCanvasHeight) {
tmpCanvasWidth = Math.max(w, tmpCanvasWidth);
tmpCanvasHeight = Math.max(h, tmpCanvasHeight);
tmpCanvas = createScratchCanvas(tmpCanvasWidth, tmpCanvasHeight);
tmpCtx = tmpCanvas.getContext('2d');
var width = image.width, height = image.height;
var fillColor = this.current.fillColor;
tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
fillColor.type === 'Pattern') ?
fillColor.getPattern(tmpCtx) : fillColor;
}
tmpCtx.fillRect(0, 0, w, h);
var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
var maskCtx = maskCanvas.getContext('2d');
maskCtx.save();
var imgData = tmpCtx.getImageData(0, 0, w, h);
var pixels = imgData.data;
putBinaryImageData(maskCtx, image);
applyStencilMask(image.data, w, h, image.inverseDecode, pixels);
maskCtx.globalCompositeOperation = 'source-in';
tmpCtx.putImageData(imgData, 0, 0);
var fillColor = this.current.fillColor;
maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
fillColor.type === 'Pattern') ?
fillColor.getPattern(maskCtx) : fillColor;
maskCtx.fillRect(0, 0, width, height);
maskCtx.restore();
ctx.save();
ctx.transform.apply(ctx, image.transform);
ctx.scale(1, -1);
ctx.drawImage(tmpCanvas, 0, 0, w, h,
ctx.drawImage(maskCanvas, 0, 0, width, height,
0, -1, 1, 1);
ctx.restore();
}
@ -1796,33 +1708,57 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var width = imgData.width;
var height = imgData.height;
var ctx = this.ctx;
this.save();
// scale the image to the unit square
ctx.scale(1 / width, -1 / height);
var currentTransform = ctx.mozCurrentTransformInverse;
var widthScale = Math.max(Math.abs(currentTransform[0]), 1);
var heightScale = Math.max(Math.abs(currentTransform[3]), 1);
var tmpCanvas = createScratchCanvas(width, height);
var tmpCtx = tmpCanvas.getContext('2d');
var a = currentTransform[0], b = currentTransform[1];
var widthScale = Math.max(Math.sqrt(a * a + b * b), 1);
var c = currentTransform[2], d = currentTransform[3];
var heightScale = Math.max(Math.sqrt(c * c + d * d), 1);
if (widthScale > 2 || heightScale > 2) {
// canvas does not resize well large images to small -- using simple
// algorithm to perform pre-scaling
tmpCanvas = prescaleImage(imgData.data,
width, height,
widthScale, heightScale);
ctx.drawImage(tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height,
0, -height, width, height);
var imgToPaint;
if (imgData instanceof HTMLElement) {
imgToPaint = imgData;
} else {
if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
tmpCtx.putImageData(imgData, 0, 0);
} else {
putBinaryImageData(tmpCtx, imgData.data, width, height);
}
ctx.drawImage(tmpCanvas, 0, -height);
var tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height);
var tmpCtx = tmpCanvas.getContext('2d');
putBinaryImageData(tmpCtx, imgData);
imgToPaint = tmpCanvas;
}
var paintWidth = width, paintHeight = height;
var tmpCanvasId = 'prescale1';
// Vertial or horizontal scaling shall not be more than 2 to not loose the
// pixels during drawImage operation, painting on the temporary canvas(es)
// that are twice smaller in size
while ((widthScale > 2 && paintWidth > 1) ||
(heightScale > 2 && paintHeight > 1)) {
var newWidth = paintWidth, newHeight = paintHeight;
if (widthScale > 2 && paintWidth > 1) {
newWidth = Math.ceil(paintWidth / 2);
widthScale /= paintWidth / newWidth;
}
if (heightScale > 2 && paintHeight > 1) {
newHeight = Math.ceil(paintHeight / 2);
heightScale /= paintHeight / newHeight;
}
var tmpCanvas = CachedCanvases.getCanvas(tmpCanvasId,
newWidth, newHeight);
tmpCtx = tmpCanvas.getContext('2d');
tmpCtx.clearRect(0, 0, newWidth, newHeight);
tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
0, 0, newWidth, newHeight);
imgToPaint = tmpCanvas;
paintWidth = newWidth;
paintHeight = newHeight;
tmpCanvasId = tmpCanvasId === 'prescale1' ? 'prescale2' : 'prescale1';
}
ctx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
0, -height, width, height);
if (this.imageLayer) {
var position = this.getCanvasPosition(0, -height);
this.imageLayer.appendImage({
@ -1842,9 +1778,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var w = imgData.width;
var h = imgData.height;
var tmpCanvas = createScratchCanvas(w, h);
var tmpCanvas = CachedCanvases.getCanvas('inlineImage', w, h);
var tmpCtx = tmpCanvas.getContext('2d');
putBinaryImageData(tmpCtx, imgData.data, w, h);
putBinaryImageData(tmpCtx, imgData);
for (var i = 0, ii = map.length; i < ii; i++) {
var entry = map[i];

View File

@ -253,7 +253,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var inverseDecode = !!decode && decode[0] > 0;
retData.fn = 'paintImageMaskXObject';
retData.args = [imgArray, inverseDecode, width, height];
retData.args = [PDFImage.createMask(imgArray, width, height,
inverseDecode)];
return retData;
}
@ -1631,9 +1632,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
for (var q = 0; q < count; q++) {
var transform = argsArray[j + (q << 2) + 1];
var maskParams = argsArray[j + (q << 2) + 2];
images.push({data: maskParams[0], width: maskParams[2],
height: maskParams[3], transform: transform,
inverseDecode: maskParams[1]});
images.push({data: maskParams.data, width: maskParams.width,
height: maskParams.height, transform: transform});
}
// replacing queue items
fnArray.splice(j, count * 4, ['paintImageMaskXObjectGroup']);

View File

@ -206,6 +206,30 @@ var PDFImage = (function PDFImageClosure() {
return temp;
};
PDFImage.createMask = function PDFImage_createMask(imgArray, width, height,
inverseDecode) {
var buffer = new Uint8Array(width * height * 4);
var imgArrayPos = 0;
var i, j, mask, buf;
// removing making non-masked pixels transparent
var bufferPos = 3; // alpha component offset
for (i = 0; i < height; i++) {
mask = 0;
for (j = 0; j < width; j++) {
if (!mask) {
buf = imgArray[imgArrayPos++];
mask = 128;
}
if (!(buf & mask) !== inverseDecode) {
buffer[bufferPos] = 255;
}
bufferPos += 4;
mask >>= 1;
}
}
return {data: buffer, width: width, height: height};
};
PDFImage.prototype = {
get drawWidth() {
if (!this.smask)
@ -362,30 +386,6 @@ var PDFImage = (function PDFImageClosure() {
}
return buf;
},
applyStencilMask: function PDFImage_applyStencilMask(buffer,
inverseDecode) {
var width = this.width, height = this.height;
var bitStrideLength = (width + 7) >> 3;
var imgArray = this.getImageBytes(bitStrideLength * height);
var imgArrayPos = 0;
var i, j, mask, buf;
// removing making non-masked pixels transparent
var bufferPos = 3; // alpha component offset
for (i = 0; i < height; i++) {
mask = 0;
for (j = 0; j < width; j++) {
if (!mask) {
buf = imgArray[imgArrayPos++];
mask = 128;
}
if (!(buf & mask) === inverseDecode) {
buffer[bufferPos] = 0;
}
bufferPos += 4;
mask >>= 1;
}
}
},
fillRgbaBuffer: function PDFImage_fillRgbaBuffer(buffer, width, height) {
var numComps = this.numComps;
var originalWidth = this.width;

View File

@ -16,14 +16,10 @@
*/
/* globals CanvasGraphics, ColorSpace, createScratchCanvas, DeviceRgbCS, error,
info, isArray, isPDFFunction, isStream, PDFFunction, TODO, Util,
warn */
warn, CachedCanvases */
'use strict';
// This global variable is used to minimize the memory usage when patterns are
// used.
var temporaryPatternCanvas = null;
var PatternType = {
AXIAL: 2,
RADIAL: 3
@ -351,9 +347,6 @@ var TilingPattern = (function TilingPatternClosure() {
// set the new canvas element context as the graphics context
var tmpCtx = tmpCanvas.getContext('2d');
// for simulated mozCurrentTransform canvas (normaly setting width/height
// will reset the matrix)
tmpCtx.setTransform(1, 0, 0, 1, 0, 0);
var graphics = new CanvasGraphics(tmpCtx, commonObjs, objs);
this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color);
@ -415,11 +408,7 @@ var TilingPattern = (function TilingPatternClosure() {
},
getPattern: function TilingPattern_getPattern() {
// The temporary canvas is created only because the memory is released
// more quickly than creating multiple temporary canvases.
if (temporaryPatternCanvas === null) {
temporaryPatternCanvas = createScratchCanvas(1, 1);
}
var temporaryPatternCanvas = CachedCanvases.getCanvas('pattern');
this.createPatternCanvas(temporaryPatternCanvas);
var ctx = this.ctx;