From 339dc6508237885635940aefda76ec6f1daf2c94 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Thu, 30 May 2013 19:42:26 -0500 Subject: [PATCH] Removes getImageData from canvas.js --- src/canvas.js | 272 ++++++++++++++++++----------------------------- src/evaluator.js | 8 +- src/image.js | 48 ++++----- src/pattern.js | 15 +-- 4 files changed, 134 insertions(+), 209 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index c6e883d68..187617b4e 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -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]; diff --git a/src/evaluator.js b/src/evaluator.js index a8aa3f3b5..c1bd5ab52 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -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']); diff --git a/src/image.js b/src/image.js index 84286d831..2d2c1e041 100644 --- a/src/image.js +++ b/src/image.js @@ -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; diff --git a/src/pattern.js b/src/pattern.js index 7c8b106dd..af0489945 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -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;