From 4e6ebf2de0d081b7a9f88fdec95293d0474d10db Mon Sep 17 00:00:00 2001 From: p01 Date: Thu, 20 Mar 2014 16:57:11 +0100 Subject: [PATCH] Optimized putBinaryImageData for GRAYSCALE_1BPP The following changes make putBinaryImageData 2.2x faster. * Use a Uint32Array to draw whole pixels instead component by component * Unroll the inner most loop * Added lazy PDFJS.hasCanvasTypedArrays, PDFJS.isLittleEndian and compatibility Uint32ArrayView for browsers using the old CanvasPixelArray --- src/display/canvas.js | 67 +++++++++++++++++++------------------ src/shared/util.js | 77 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 32 deletions(-) diff --git a/src/display/canvas.js b/src/display/canvas.js index 6d60a7d7e..bcb851640 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -14,10 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* globals ColorSpace, DeviceCmykCS, DeviceGrayCS, DeviceRgbCS, error, - FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, ImageData, ImageKind, - isArray, isNum, TilingPattern, OPS, Promise, Util, warn, assert, - info, shadow, TextRenderingMode, getShadingPatternFromIR */ +/* globals ColorSpace, DeviceCmykCS, DeviceGrayCS, DeviceRgbCS, error, PDFJS, + FONT_IDENTITY_MATRIX, Uint32ArrayView, IDENTITY_MATRIX, ImageData, + ImageKind, isArray, isNum, TilingPattern, OPS, Promise, Util, warn, + assert, info, shadow, TextRenderingMode, getShadingPatternFromIR */ 'use strict'; @@ -463,45 +463,48 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // Grayscale, 1 bit per pixel (i.e. black-and-white). var destDataLength = dest.length; var srcLength = src.byteLength; - for (var i = 3; i < destDataLength; i += 4) { - dest[i] = 255; - } + var dest32 = PDFJS.hasCanvasTypedArrays ? new Uint32Array(dest.buffer) : + new Uint32ArrayView(dest); + var dest32DataLength = dest32.length; + var fullSrcDiff = (width + 7) >> 3; + var white = 0xFFFFFFFF; + var black = (PDFJS.isLittleEndian || !PDFJS.hasCanvasTypedArrays) ? + 0xFF000000 : 0x000000FF; for (var i = 0; i < totalChunks; i++) { var thisChunkHeight = (i < fullChunks) ? fullChunkHeight : partialChunkHeight; var destPos = 0; for (var j = 0; j < thisChunkHeight; j++) { + var srcDiff = srcLength - srcPos; + var k = 0; + var kEnd = (srcDiff > fullSrcDiff) ? width : srcDiff * 8 - 7; + var kEndUnrolled = kEnd & ~7; var mask = 0; var srcByte = 0; - for (var k = 0; k < width; k++, destPos += 4) { - if (mask === 0) { - if (srcPos >= srcLength) { - break; - } - srcByte = src[srcPos++]; - mask = 128; - } - - if ((srcByte & mask)) { - dest[destPos] = 255; - dest[destPos + 1] = 255; - dest[destPos + 2] = 255; - } else { - dest[destPos] = 0; - dest[destPos + 1] = 0; - dest[destPos + 2] = 0; - } + for (; k < kEndUnrolled; k += 8) { + srcByte = src[srcPos++]; + dest32[destPos++] = (srcByte & 128) ? white : black; + dest32[destPos++] = (srcByte & 64) ? white : black; + dest32[destPos++] = (srcByte & 32) ? white : black; + dest32[destPos++] = (srcByte & 16) ? white : black; + dest32[destPos++] = (srcByte & 8) ? white : black; + dest32[destPos++] = (srcByte & 4) ? white : black; + dest32[destPos++] = (srcByte & 2) ? white : black; + dest32[destPos++] = (srcByte & 1) ? white : black; + } + for (; k < kEnd; k++) { + if (mask === 0) { + srcByte = src[srcPos++]; + mask = 128; + } + dest32[destPos++] = (srcByte & mask) ? white : black; mask >>= 1; } } - if (destPos < destDataLength) { - // We ran out of input. Make all remaining pixels transparent. - destPos += 3; - do { - dest[destPos] = 0; - destPos += 4; - } while (destPos < destDataLength); + // We ran out of input. Make all remaining pixels transparent. + while (destPos < dest32DataLength) { + dest32[destPos++] = 0; } ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); diff --git a/src/shared/util.js b/src/shared/util.js index c645d5838..a943d476d 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -410,6 +410,83 @@ function stringToBytes(str) { return bytes; } +// Lazy test the endianness of the platform +// NOTE: This will be 'true' for simulated TypedArrays +function isLittleEndian() { + var buffer8 = new Uint8Array(2); + buffer8[0] = 1; + var buffer16 = new Uint16Array(buffer8.buffer); + return (buffer16[0] === 1); +} + +Object.defineProperty(PDFJS, 'isLittleEndian', { + configurable: true, + get: function PDFJS_isLittleEndian() { + return shadow(PDFJS, 'isLittleEndian', isLittleEndian()); + } +}); + +//#if !(FIREFOX || MOZCENTRAL || B2G || CHROME) +// Lazy test if the userAgant support CanvasTypedArrays +function hasCanvasTypedArrays() { + var canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + var ctx = canvas.getContext('2d'); + var imageData = ctx.createImageData(1, 1); + return (typeof imageData.data.buffer !== 'undefined'); +} + +Object.defineProperty(PDFJS, 'hasCanvasTypedArrays', { + configurable: true, + get: function PDFJS_hasCanvasTypedArrays() { + return shadow(PDFJS, 'hasCanvasTypedArrays', hasCanvasTypedArrays()); + } +}); + +// Uint32ArrayView +var Uint32ArrayView = (function Uint32ArrayViewClosure() { + + function Uint32ArrayView(buffer) { + this.buffer = buffer; + this.byteLength = buffer.length; + this.length = (this.byteLength >> 2); + ensureUint32ArrayViewProps(this.length); + } + Uint32ArrayView.prototype = Object.create(null); + + var uint32ArrayViewSetters = 0; + function createUint32ArrayProp(index) { + return { + get: function () { + var buffer = this.buffer, offset = index << 2; + return (buffer[offset] | (buffer[offset + 1] << 8) | + (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24)) >>> 0; + }, + set: function (value) { + var buffer = this.buffer, offset = index << 2; + buffer[offset] = value & 255; + buffer[offset + 1] = (value >> 8) & 255; + buffer[offset + 2] = (value >> 16) & 255; + buffer[offset + 3] = (value >>> 24) & 255; + } + }; + } + + function ensureUint32ArrayViewProps(length) { + while (uint32ArrayViewSetters < length) { + Object.defineProperty(Uint32ArrayView.prototype, + uint32ArrayViewSetters, + createUint32ArrayProp(uint32ArrayViewSetters)); + uint32ArrayViewSetters++; + } + } + + return Uint32ArrayView; +})(); +//#else +//PDFJS.hasCanvasTypedArrays = true; +//#endif + var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; var Util = PDFJS.Util = (function UtilClosure() {