From f62c1c469fb31e113469d3af38b2c1818748e085 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 24 Feb 2014 19:37:19 -0800 Subject: [PATCH 1/3] Special-case 24-bit RGB image-handling. --- src/core/evaluator.js | 6 +++-- src/core/image.js | 52 ++++++++++++++++++++++++++----------------- src/display/canvas.js | 16 +++++++++++++ 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 2e85cbd16..2f76c26c7 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -166,7 +166,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { (w + h) < SMALL_IMAGE_DIMENSIONS) { var imageObj = new PDFImage(this.xref, resources, image, inline, null, null); - var imgData = imageObj.createImageData(); + // We force the use of 'rgba_32bpp' images here, because we can't + // handle any other kind. + var imgData = imageObj.createImageData(/* forceRGBA = */ true); operatorList.addOp(OPS.paintInlineImageXObject, [imgData]); return; } @@ -189,7 +191,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { PDFImage.buildImage(function(imageObj) { - var imgData = imageObj.createImageData(); + var imgData = imageObj.createImageData(/* forceRGBA = */ false); self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData], null, [imgData.data.buffer]); }, self.handler, self.xref, resources, image, inline); diff --git a/src/core/image.js b/src/core/image.js index 1adad5e29..0655f87fe 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -417,7 +417,7 @@ var PDFImage = (function PDFImageClosure() { buffer[i + 2] = clamp((buffer[i + 2] - matteRgb[2]) * k + matteRgb[2]); } }, - createImageData: function PDFImage_createImageData() { + createImageData: function PDFImage_createImageData(forceRGBA) { var drawWidth = this.drawWidth; var drawHeight = this.drawHeight; var imgData = { // other fields are filled in below @@ -430,32 +430,42 @@ var PDFImage = (function PDFImageClosure() { var originalHeight = this.height; var bpc = this.bpc; - // rows start at byte boundary; + // Rows start at byte boundary. var rowBytes = (originalWidth * numComps * bpc + 7) >> 3; var imgArray = this.getImageBytes(originalHeight * rowBytes); - // imgArray can be incomplete (e.g. after CCITT fax encoding) + if (!forceRGBA) { + // If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image + // without any complications, we pass a same-sized copy to the main + // thread rather than expanding by 32x to RGBA form. This saves *lots* + // of memory for many scanned documents. It's also much faster. + // + // Similarly, if it is a 24-bit-per pixel RGB image without any + // complications, we avoid expanding by 1.333x to RGBA form. + var kind; + if (this.colorSpace.name === 'DeviceGray' && bpc === 1) { + kind = 'grayscale_1bpp'; + } else if (this.colorSpace.name === 'DeviceRGB' && bpc === 8) { + kind = 'rgb_24bpp'; + } + if (kind && !this.smask && !this.mask && !this.needsDecode && + drawWidth === originalWidth && drawHeight === originalHeight) { + imgData.kind = kind; + + // We must make a copy of imgArray, otherwise it'll be neutered upon + // transfer which will break any code that subsequently reuses it. + var newArray = new Uint8Array(imgArray.length); + newArray.set(imgArray); + imgData.data = newArray; + imgData.origLength = imgArray.length; + return imgData; + } + } + + // imgArray can be incomplete (e.g. after CCITT fax encoding). var actualHeight = 0 | (imgArray.length / rowBytes * drawHeight / originalHeight); - // If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image - // without any complications, we pass a same-sized copy to the main - // thread rather than expanding by 32x to RGBA form. This saves *lots* of - // memory for many scanned documents. It's also much faster. - if (this.colorSpace.name === 'DeviceGray' && bpc === 1 && - !this.smask && !this.mask && !this.needsDecode && - drawWidth === originalWidth && drawHeight === originalHeight) { - imgData.kind = 'grayscale_1bpp'; - - // We must make a copy of imgArray, otherwise it'll be neutered upon - // transfer which will break any code that subsequently reuses it. - var newArray = new Uint8Array(imgArray.length); - newArray.set(imgArray); - imgData.data = newArray; - imgData.origLength = imgArray.length; - return imgData; - } - var comps = this.getComponents(imgArray); var rgbaBuf = new Uint8Array(drawWidth * drawHeight * 4); diff --git a/src/display/canvas.js b/src/display/canvas.js index 7381aa691..5585cfa64 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -526,6 +526,22 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); } + } else if (imgData.kind === 'rgb_24bpp') { + // RGB, 24-bits per pixel. + for (var i = 0; i < totalChunks; i++) { + var thisChunkHeight = + (i < fullChunks) ? fullChunkHeight : partialChunkHeight; + var elemsInThisChunk = imgData.width * thisChunkHeight * 3; + var destPos = 0; + for (var j = 0; j < elemsInThisChunk; j += 3) { + chunkImgData.data[destPos++] = imgData.data[srcPos++]; + chunkImgData.data[destPos++] = imgData.data[srcPos++]; + chunkImgData.data[destPos++] = imgData.data[srcPos++]; + chunkImgData.data[destPos++] = 255; + } + ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); + } + } else { error('bad image kind: ' + imgData.kind); } From 4e1f92a893f3f3a0f4914e24c75eba0daab90f1a Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 24 Feb 2014 19:41:04 -0800 Subject: [PATCH 2/3] Clean up putBinaryImageData(). --- src/core/image.js | 1 - src/display/canvas.js | 42 ++++++++++++++++++++---------------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/core/image.js b/src/core/image.js index 0655f87fe..ef4c319d4 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -457,7 +457,6 @@ var PDFImage = (function PDFImageClosure() { var newArray = new Uint8Array(imgArray.length); newArray.set(imgArray); imgData.data = newArray; - imgData.origLength = imgArray.length; return imgData; } } diff --git a/src/display/canvas.js b/src/display/canvas.js index 5585cfa64..a299321e6 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -452,19 +452,17 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var chunkImgData = ctx.createImageData(width, fullChunkHeight); var srcPos = 0; var src = imgData.data; - var dst = chunkImgData.data; + var dest = chunkImgData.data; // There are multiple forms in which the pixel data can be passed, and // imgData.kind tells us which one this is. if (imgData.kind === 'grayscale_1bpp') { // Grayscale, 1 bit per pixel (i.e. black-and-white). - var srcData = imgData.data; - var destData = chunkImgData.data; - var destDataLength = destData.length; - var origLength = imgData.origLength; + var destDataLength = dest.length; + var srcLength = src.byteLength; for (var i = 3; i < destDataLength; i += 4) { - destData[i] = 255; + dest[i] = 255; } for (var i = 0; i < totalChunks; i++) { var thisChunkHeight = @@ -475,21 +473,21 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var srcByte = 0; for (var k = 0; k < width; k++, destPos += 4) { if (mask === 0) { - if (srcPos >= origLength) { + if (srcPos >= srcLength) { break; } - srcByte = srcData[srcPos++]; + srcByte = src[srcPos++]; mask = 128; } if ((srcByte & mask)) { - destData[destPos] = 255; - destData[destPos + 1] = 255; - destData[destPos + 2] = 255; + dest[destPos] = 255; + dest[destPos + 1] = 255; + dest[destPos + 2] = 255; } else { - destData[destPos] = 0; - destData[destPos + 1] = 0; - destData[destPos + 2] = 0; + dest[destPos] = 0; + dest[destPos + 1] = 0; + dest[destPos + 2] = 0; } mask >>= 1; @@ -499,7 +497,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // We ran out of input. Make all remaining pixels transparent. destPos += 3; do { - destData[destPos] = 0; + dest[destPos] = 0; destPos += 4; } while (destPos < destDataLength); } @@ -509,18 +507,18 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } else if (imgData.kind === 'rgba_32bpp') { // RGBA, 32-bits per pixel. - var haveSetAndSubarray = 'set' in dst && 'subarray' in src; + var haveSetAndSubarray = 'set' in dest && 'subarray' in src; for (var i = 0; i < totalChunks; i++) { var thisChunkHeight = (i < fullChunks) ? fullChunkHeight : partialChunkHeight; var elemsInThisChunk = imgData.width * thisChunkHeight * 4; if (haveSetAndSubarray) { - dst.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); + dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); srcPos += elemsInThisChunk; } else { for (var j = 0; j < elemsInThisChunk; j++) { - chunkImgData.data[j] = imgData.data[srcPos++]; + dest[j] = src[srcPos++]; } } ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); @@ -534,10 +532,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var elemsInThisChunk = imgData.width * thisChunkHeight * 3; var destPos = 0; for (var j = 0; j < elemsInThisChunk; j += 3) { - chunkImgData.data[destPos++] = imgData.data[srcPos++]; - chunkImgData.data[destPos++] = imgData.data[srcPos++]; - chunkImgData.data[destPos++] = imgData.data[srcPos++]; - chunkImgData.data[destPos++] = 255; + dest[destPos++] = src[srcPos++]; + dest[destPos++] = src[srcPos++]; + dest[destPos++] = src[srcPos++]; + dest[destPos++] = 255; } ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); } From 42cbb5b4406edaa8a9bf810c1e14135366e588f5 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 25 Feb 2014 15:11:15 -0800 Subject: [PATCH 3/3] Introduce ImageKind constants. --- src/core/evaluator.js | 8 ++++---- src/core/image.js | 10 +++++----- src/display/canvas.js | 12 ++++++------ src/shared/util.js | 6 ++++++ 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 2f76c26c7..8a8543381 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -16,7 +16,7 @@ */ /* globals assert, assertWellFormed, ColorSpace, Dict, Encodings, error, ErrorFont, Font, FONT_IDENTITY_MATRIX, fontCharsToUnicode, FontFlags, - info, isArray, isCmd, isDict, isEOF, isName, isNum, + ImageKind, info, isArray, isCmd, isDict, isEOF, isName, isNum, isStream, isString, JpegStream, Lexer, Metrics, Name, Parser, Pattern, PDFImage, PDFJS, serifFonts, stdFontMap, symbolsFonts, getTilingPatternIR, warn, Util, Promise, LegacyPromise, @@ -166,8 +166,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { (w + h) < SMALL_IMAGE_DIMENSIONS) { var imageObj = new PDFImage(this.xref, resources, image, inline, null, null); - // We force the use of 'rgba_32bpp' images here, because we can't - // handle any other kind. + // We force the use of RGBA_32BPP images here, because we can't handle + // any other kind. var imgData = imageObj.createImageData(/* forceRGBA = */ true); operatorList.addOp(OPS.paintInlineImageXObject, [imgData]); return; @@ -1321,7 +1321,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // replacing queue items squash(fnArray, j, count * 4, OPS.paintInlineImageXObjectGroup); argsArray.splice(j, count * 4, - [{width: imgWidth, height: imgHeight, kind: 'rgba_32bpp', + [{width: imgWidth, height: imgHeight, kind: ImageKind.RGBA_32BPP, data: imgData}, map]); i = j; ii = argsArray.length; diff --git a/src/core/image.js b/src/core/image.js index ef4c319d4..084e6e339 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -14,8 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* globals ColorSpace, error, isArray, isStream, JpegStream, Name, Promise, - Stream, warn, LegacyPromise */ +/* globals ColorSpace, error, isArray, ImageKind, isStream, JpegStream, Name, + Promise, Stream, warn, LegacyPromise */ 'use strict'; @@ -444,9 +444,9 @@ var PDFImage = (function PDFImageClosure() { // complications, we avoid expanding by 1.333x to RGBA form. var kind; if (this.colorSpace.name === 'DeviceGray' && bpc === 1) { - kind = 'grayscale_1bpp'; + kind = ImageKind.GRAYSCALE_1BPP; } else if (this.colorSpace.name === 'DeviceRGB' && bpc === 8) { - kind = 'rgb_24bpp'; + kind = ImageKind.RGB_24BPP; } if (kind && !this.smask && !this.mask && !this.needsDecode && drawWidth === originalWidth && drawHeight === originalHeight) { @@ -482,7 +482,7 @@ var PDFImage = (function PDFImageClosure() { this.undoPreblend(rgbaBuf, drawWidth, actualHeight); - imgData.kind = 'rgba_32bpp'; + imgData.kind = ImageKind.RGBA_32BPP; imgData.data = rgbaBuf; return imgData; }, diff --git a/src/display/canvas.js b/src/display/canvas.js index a299321e6..dbf5f7161 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -15,9 +15,9 @@ * limitations under the License. */ /* globals ColorSpace, DeviceCmykCS, DeviceGrayCS, DeviceRgbCS, error, - FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageData, isArray, isNum, - TilingPattern, OPS, Promise, Util, warn, assert, info, shadow, - TextRenderingMode, getShadingPatternFromIR */ + FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageData, ImageKind, + isArray, isNum, TilingPattern, OPS, Promise, Util, warn, assert, + info, shadow, TextRenderingMode, getShadingPatternFromIR */ 'use strict'; @@ -457,7 +457,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // There are multiple forms in which the pixel data can be passed, and // imgData.kind tells us which one this is. - if (imgData.kind === 'grayscale_1bpp') { + if (imgData.kind === ImageKind.GRAYSCALE_1BPP) { // Grayscale, 1 bit per pixel (i.e. black-and-white). var destDataLength = dest.length; var srcLength = src.byteLength; @@ -505,7 +505,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); } - } else if (imgData.kind === 'rgba_32bpp') { + } else if (imgData.kind === ImageKind.RGBA_32BPP) { // RGBA, 32-bits per pixel. var haveSetAndSubarray = 'set' in dest && 'subarray' in src; @@ -524,7 +524,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); } - } else if (imgData.kind === 'rgb_24bpp') { + } else if (imgData.kind === ImageKind.RGB_24BPP) { // RGB, 24-bits per pixel. for (var i = 0; i < totalChunks; i++) { var thisChunkHeight = diff --git a/src/shared/util.js b/src/shared/util.js index 312445565..f3f6289ca 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -38,6 +38,12 @@ var TextRenderingMode = { ADD_TO_PATH_FLAG: 4 }; +var ImageKind = { + GRAYSCALE_1BPP: 1, + RGB_24BPP: 2, + RGBA_32BPP: 3 +}; + // The global PDFJS object exposes the API // In production, it will be declared outside a global wrapper // In development, it will be declared here