From e7fe45a5c455393259d93dc93f1d4c826edc5237 Mon Sep 17 00:00:00 2001 From: Thorben Bochenek Date: Thu, 27 Mar 2014 11:11:28 +0100 Subject: [PATCH 1/2] Refactor jpg.js and include forceRGBoutput, correct style of image.js This refactors getData to be more readable and extracts all the color conversion algorithms to their own functions. The resulting code was then cleaned up. This also introduces a flag `forceRGBoutput` to getData, that allows to always get the data as a `width * height * 3` bytes long RGB buffer --- external/jpgjs/jpg.js | 195 ++++++++++++++++++++++++------------------ src/core/image.js | 15 +++- 2 files changed, 122 insertions(+), 88 deletions(-) diff --git a/external/jpgjs/jpg.js b/external/jpgjs/jpg.js index 549097b26..bd8510ad2 100644 --- a/external/jpgjs/jpg.js +++ b/external/jpgjs/jpg.js @@ -782,9 +782,10 @@ var JpegImage = (function jpegImage() { blocksPerColumn: component.blocksPerColumn }); } + this.numComponents = this.components.length; }, - getData: function getData(width, height) { + _getLinearizedBlockData: function getLinearizedBlockData(width, height) { var scaleX = this.width / width, scaleY = this.height / height; var component, componentScaleX, componentScaleY; @@ -842,80 +843,124 @@ var JpegImage = (function jpegImage() { } } } + return data; + }, - // ... then transform colors, if necessary - switch (numComponents) { - case 1: case 2: break; - // no color conversion for one or two compoenents + _isColorConversionNeeded: function isColorConversionNeeded() { + if (this.adobe && this.adobe.transformCode) { + // The adobe transform marker overrides any previous setting + return true; + } else if (this.numComponents == 3) { + return true; + } else { + return false; + } + }, - case 3: - // The default transform for three components is true - colorTransform = true; - // The adobe transform marker overrides any previous setting - if (this.adobe && this.adobe.transformCode) - colorTransform = true; - else if (typeof this.colorTransform !== 'undefined') - colorTransform = !!this.colorTransform; - - if (colorTransform) { - for (i = 0; i < dataLength; i += numComponents) { - Y = data[i ]; - Cb = data[i + 1]; - Cr = data[i + 2]; - - R = clamp0to255(Y + 1.402 * (Cr - 128)); - G = clamp0to255(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); - B = clamp0to255(Y + 1.772 * (Cb - 128)); - - data[i ] = R; - data[i + 1] = G; - data[i + 2] = B; - } - } - break; - case 4: - // The default transform for four components is false - colorTransform = false; - // The adobe transform marker overrides any previous setting - if (this.adobe && this.adobe.transformCode) - colorTransform = true; - else if (typeof this.colorTransform !== 'undefined') - colorTransform = !!this.colorTransform; - - if (colorTransform) { - for (i = 0; i < dataLength; i += numComponents) { - Y = data[i]; - Cb = data[i + 1]; - Cr = data[i + 2]; - - C = 255 - clamp0to255(Y + 1.402 * (Cr - 128)); - M = 255 - clamp0to255(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); - Ye = 255 - clamp0to255(Y + 1.772 * (Cb - 128)); - - data[i ] = C; - data[i + 1] = M; - data[i + 2] = Ye; - // K is unchanged - } - } - break; - default: - throw 'Unsupported color mode'; + _convertYccToRgb: function convertYccToRgb(data) { + var Y, Cb, Cr; + for (var i = 0; i < data.length; i += this.numComponents) { + Y = data[i ]; + Cb = data[i + 1]; + Cr = data[i + 2]; + data[i ] = clamp0to255(Y + 1.402 * (Cr - 128)); + data[i + 1] = clamp0to255(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); + data[i + 2] = clamp0to255(Y + 1.772 * (Cb - 128)); } return data; }, + + _convertYcckToRgb: function convertYcckToRgb(data) { + var Y, Cb, Cr, K, C, M, Ye, oneMinusK255th; + var outputData = new Uint8Array((data.length / 4) * 3); + var offset = 0; + for (var i = 0; i < data.length; i += this.numComponents) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + K = data[i + 3]; + C = 255 - clamp0to255(Y + 1.402 * (Cr - 128)); + M = 255 - clamp0to255(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); + Ye = 255 - clamp0to255(Y + 1.772 * (Cb - 128)); + oneMinusK255th = (1 - K / 255); + outputData[offset++] = 255 - clamp0to255(C * oneMinusK255th + K); + outputData[offset++] = 255 - clamp0to255(M * oneMinusK255th + K); + outputData[offset++] = 255 - clamp0to255(Ye * oneMinusK255th + K); + } + return outputData; + }, + + _convertYcckToCmyk: function convertYcckToCmyk(data) { + var Y, Cb, Cr; + for (var i = 0; i < data.length; i += this.numComponents) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + data[i ] = 255 - clamp0to255(Y + 1.402 * (Cr - 128)); + data[i + 1] = 255 - clamp0to255(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); + data[i + 2] = 255 - clamp0to255(Y + 1.772 * (Cb - 128)); + // K in data[i + 3] is unchanged + } + return data; + }, + + _convertCmykToRgb: function convertCmykToRgb(data) { + var C, M, Y, K, oneMinusK255th; + var outputData = new Uint8Array((data.length / 4) * 3); + var offset = 0; + for (var i = 0; i < data.length; i += this.numComponents) { + C = data[i ]; + M = data[i + 1]; + Y = data[i + 2]; + K = data[i + 3]; + oneMinusK255th = (1 - K / 255); + outputData[offset++] = 255 - clamp0to255(C * oneMinusK255th + K); + outputData[offset++] = 255 - clamp0to255(M * oneMinusK255th + K); + outputData[offset++] = 255 - clamp0to255(Y * oneMinusK255th + K); + // K in data[i + 3] is unchanged + } + return outputData; + }, + + getData: function getData(width, height, forceRGBoutput) { + var i; + var Y, Cb, Cr, K, C, M, Ye, R, G, B; + var colorTransform; + if (this.numComponents > 4) { + throw 'Unsupported color mode'; + } + // type of data: Uint8Array(width * height * numComponents) + var data = this._getLinearizedBlockData(width, height); + + if (this.numComponents === 3) { + return this._convertYccToRgb(data); + } else if (this.numComponents === 4) { + if (this._isColorConversionNeeded()) { + if (forceRGBoutput) { + return this._convertYcckToRgb(data); + } else { + return this._convertYcckToCmyk(data); + } + } else { + return this._convertCmykToRgb(data); + } + } + return data; + }, + copyToImageData: function copyToImageData(imageData) { - var width = imageData.width, height = imageData.height; + var width = imageData.width; + var height = imageData.height; var imageDataBytes = width * height * 4; var imageDataArray = imageData.data; - var data = this.getData(width, height); + var data = this.getData(width, height, /* forceRGBoutput = */true); + var i = 0, j = 0; - var Y, K, C, M, R, G, B; switch (this.components.length) { case 1: + var Y; while (j < imageDataBytes) { Y = data[i++]; - imageDataArray[j++] = Y; imageDataArray[j++] = Y; imageDataArray[j++] = Y; @@ -923,31 +968,11 @@ var JpegImage = (function jpegImage() { } break; case 3: - while (j < imageDataBytes) { - R = data[i++]; - G = data[i++]; - B = data[i++]; - - imageDataArray[j++] = R; - imageDataArray[j++] = G; - imageDataArray[j++] = B; - imageDataArray[j++] = 255; - } - break; case 4: while (j < imageDataBytes) { - C = data[i++]; - M = data[i++]; - Y = data[i++]; - K = data[i++]; - - R = 255 - clamp0to255(C * (1 - K / 255) + K); - G = 255 - clamp0to255(M * (1 - K / 255) + K); - B = 255 - clamp0to255(Y * (1 - K / 255) + K); - - imageDataArray[j++] = R; - imageDataArray[j++] = G; - imageDataArray[j++] = B; + imageDataArray[j++] = data[i++]; + imageDataArray[j++] = data[i++]; + imageDataArray[j++] = data[i++]; imageDataArray[j++] = 255; } break; diff --git a/src/core/image.js b/src/core/image.js index 333aabb3f..f3350bac7 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -44,6 +44,7 @@ var PDFImage = (function PDFImageClosure() { return Promise.resolve(image); } } + /** * Decode and clamp a value. The formula is different from the spec because we * don't decode to float range [0,1], we decode it in the [0,max] range. @@ -53,6 +54,7 @@ var PDFImage = (function PDFImageClosure() { // Clamp the value to the range return (value < 0 ? 0 : (value > max ? max : value)); } + function PDFImage(xref, res, image, inline, smask, mask, isMask) { this.image = image; var dict = image.dict; @@ -279,11 +281,13 @@ var PDFImage = (function PDFImageClosure() { this.smask && this.smask.width || 0, this.mask && this.mask.width || 0); }, + get drawHeight() { return Math.max(this.height, this.smask && this.smask.height || 0, this.mask && this.mask.height || 0); }, + decodeBuffer: function PDFImage_decodeBuffer(buffer) { var bpc = this.bpc; var numComps = this.numComps; @@ -309,6 +313,7 @@ var PDFImage = (function PDFImageClosure() { } } }, + getComponents: function PDFImage_getComponents(buffer) { var bpc = this.bpc; @@ -385,6 +390,7 @@ var PDFImage = (function PDFImageClosure() { } return output; }, + fillOpacity: function PDFImage_fillOpacity(rgbaBuf, width, height, actualHeight, image) { var smask = this.smask; @@ -451,6 +457,7 @@ var PDFImage = (function PDFImageClosure() { } } }, + undoPreblend: function PDFImage_undoPreblend(buffer, width, height) { var matte = this.smask && this.smask.matte; if (!matte) { @@ -467,7 +474,7 @@ var PDFImage = (function PDFImageClosure() { var alpha = buffer[i + 3]; if (alpha === 0) { // according formula we have to get Infinity in all components - // making it as white (tipical paper color) should be okay + // making it white (tipical paper color) should be okay buffer[i] = 255; buffer[i + 1] = 255; buffer[i + 2] = 255; @@ -479,6 +486,7 @@ var PDFImage = (function PDFImageClosure() { buffer[i + 2] = clamp((buffer[i + 2] - matteRgb[2]) * k + matteRgb[2]); } }, + createImageData: function PDFImage_createImageData(forceRGBA) { var drawWidth = this.drawWidth; var drawHeight = this.drawHeight; @@ -529,7 +537,6 @@ var PDFImage = (function PDFImageClosure() { return imgData; } } - // imgArray can be incomplete (e.g. after CCITT fax encoding). var actualHeight = 0 | (imgArray.length / rowBytes * drawHeight / originalHeight); @@ -567,6 +574,7 @@ var PDFImage = (function PDFImageClosure() { return imgData; }, + fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) { var numComps = this.numComps; if (numComps != 1) { @@ -589,7 +597,7 @@ var PDFImage = (function PDFImageClosure() { length = width * height; if (this.needsDecode) { // invert and scale to {0, 255} - for (i = 0; i < length; ++i) { + for (var i = 0; i < length; ++i) { buffer[i] = (comps[i] - 1) & 255; } } else { @@ -611,6 +619,7 @@ var PDFImage = (function PDFImageClosure() { buffer[i] = (scale * comps[i]) | 0; } }, + getImageBytes: function PDFImage_getImageBytes(length) { this.image.reset(); return this.image.getBytes(length); From e8f0700bfaa875817f3f460b64435c5b80943e0d Mon Sep 17 00:00:00 2001 From: Thorben Bochenek Date: Thu, 27 Mar 2014 17:08:32 +0100 Subject: [PATCH 2/2] Move the colour conversion to jpg.js Benchmarking shows that this improves performance for the invitation document from https://github.com/mozilla/pdf.js/issues/3809 by 35% --- external/jpgjs/LICENSE | 17 -- src/core/image.js | 18 +- {external/jpgjs => src/core}/jpg.js | 379 ++++++++++++++++------------ src/core/stream.js | 11 +- src/worker_loader.js | 4 +- test/font/font_test.html | 2 +- test/unit/unit_test.html | 2 +- 7 files changed, 243 insertions(+), 190 deletions(-) delete mode 100644 external/jpgjs/LICENSE rename {external/jpgjs => src/core}/jpg.js (73%) diff --git a/external/jpgjs/LICENSE b/external/jpgjs/LICENSE deleted file mode 100644 index 1e29c87c8..000000000 --- a/external/jpgjs/LICENSE +++ /dev/null @@ -1,17 +0,0 @@ - - Copyright (C) 2011 notmasteryet - - Contributors: Yury Delendik - Brendan Dahl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/core/image.js b/src/core/image.js index f3350bac7..bb9b11ac2 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -502,7 +502,7 @@ var PDFImage = (function PDFImageClosure() { // Rows start at byte boundary. var rowBytes = (originalWidth * numComps * bpc + 7) >> 3; - var imgArray = this.getImageBytes(originalHeight * rowBytes); + var imgArray; if (!forceRGBA) { // If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image @@ -522,6 +522,7 @@ var PDFImage = (function PDFImageClosure() { drawWidth === originalWidth && drawHeight === originalHeight) { imgData.kind = kind; + imgArray = this.getImageBytes(originalHeight * rowBytes); // If imgArray came from a DecodeStream, we're safe to transfer it // (and thus neuter it) because it will constitute the entire // DecodeStream's data. But if it came from a Stream, we need to @@ -537,6 +538,14 @@ var PDFImage = (function PDFImageClosure() { return imgData; } } + if (this.image instanceof JpegStream) { + imgData.kind = ImageKind.RGB_24BPP; + imgData.data = this.getImageBytes(originalHeight * rowBytes, + drawWidth, drawHeight); + return imgData; + } + + imgArray = this.getImageBytes(originalHeight * rowBytes); // imgArray can be incomplete (e.g. after CCITT fax encoding). var actualHeight = 0 | (imgArray.length / rowBytes * drawHeight / originalHeight); @@ -597,7 +606,7 @@ var PDFImage = (function PDFImageClosure() { length = width * height; if (this.needsDecode) { // invert and scale to {0, 255} - for (var i = 0; i < length; ++i) { + for (i = 0; i < length; ++i) { buffer[i] = (comps[i] - 1) & 255; } } else { @@ -620,8 +629,11 @@ var PDFImage = (function PDFImageClosure() { } }, - getImageBytes: function PDFImage_getImageBytes(length) { + getImageBytes: function PDFImage_getImageBytes(length, + drawWidth, drawHeight) { this.image.reset(); + this.image.drawWidth = drawWidth; + this.image.drawHeight = drawHeight; return this.image.getBytes(length); } }; diff --git a/external/jpgjs/jpg.js b/src/core/jpg.js similarity index 73% rename from external/jpgjs/jpg.js rename to src/core/jpg.js index bd8510ad2..5ec061184 100644 --- a/external/jpgjs/jpg.js +++ b/src/core/jpg.js @@ -1,31 +1,37 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +/* Copyright 2014 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /* - Copyright 2011 notmasteryet +This code was forked from https://github.com/notmasteryet/jpgjs. The original +version was created by github user notmasteryet - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +- The JPEG specification can be found in the ITU CCITT Recommendation T.81 + (www.w3.org/Graphics/JPEG/itu-t81.pdf) +- The JFIF specification can be found in the JPEG File Interchange Format + (www.w3.org/Graphics/JPEG/jfif3.pdf) +- The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters + in PostScript Level 2, Technical Note #5116 + (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) */ -// - The JPEG specification can be found in the ITU CCITT Recommendation T.81 -// (www.w3.org/Graphics/JPEG/itu-t81.pdf) -// - The JFIF specification can be found in the JPEG File Interchange Format -// (www.w3.org/Graphics/JPEG/jfif3.pdf) -// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters -// in PostScript Level 2, Technical Note #5116 -// (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) +'use strict'; var JpegImage = (function jpegImage() { - "use strict"; var dctZigZag = new Int32Array([ 0, 1, 8, @@ -44,22 +50,23 @@ var JpegImage = (function jpegImage() { 63 ]); - var dctCos1 = 4017 // cos(pi/16) - var dctSin1 = 799 // sin(pi/16) - var dctCos3 = 3406 // cos(3*pi/16) - var dctSin3 = 2276 // sin(3*pi/16) - var dctCos6 = 1567 // cos(6*pi/16) - var dctSin6 = 3784 // sin(6*pi/16) - var dctSqrt2 = 5793 // sqrt(2) - var dctSqrt1d2 = 2896 // sqrt(2) / 2 + var dctCos1 = 4017; // cos(pi/16) + var dctSin1 = 799; // sin(pi/16) + var dctCos3 = 3406; // cos(3*pi/16) + var dctSin3 = 2276; // sin(3*pi/16) + var dctCos6 = 1567; // cos(6*pi/16) + var dctSin6 = 3784; // sin(6*pi/16) + var dctSqrt2 = 5793; // sqrt(2) + var dctSqrt1d2 = 2896; // sqrt(2) / 2 function constructor() { } function buildHuffmanTable(codeLengths, values) { var k = 0, code = [], i, j, length = 16; - while (length > 0 && !codeLengths[length - 1]) + while (length > 0 && !codeLengths[length - 1]) { length--; + } code.push({children: [], index: 0}); var p = code[0], q; for (i = 0; i < length; i++) { @@ -114,7 +121,8 @@ var JpegImage = (function jpegImage() { if (bitsData == 0xFF) { var nextByte = data[offset++]; if (nextByte) { - throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16); + throw 'unexpected marker: ' + + ((bitsData << 8) | nextByte).toString(16); } // unstuff 0 } @@ -127,10 +135,12 @@ var JpegImage = (function jpegImage() { var bit; while ((bit = readBit()) !== null) { node = node[bit]; - if (typeof node === 'number') + if (typeof node === 'number') { return node; - if (typeof node !== 'object') - throw "invalid huffman sequence"; + } + if (typeof node !== 'object') { + throw 'invalid huffman sequence'; + } } return null; } @@ -139,7 +149,9 @@ var JpegImage = (function jpegImage() { var n = 0; while (length > 0) { var bit = readBit(); - if (bit === null) return; + if (bit === null) { + return; + } n = (n << 1) | bit; length--; } @@ -148,8 +160,9 @@ var JpegImage = (function jpegImage() { function receiveAndExtend(length) { var n = receive(length); - if (n >= 1 << (length - 1)) + if (n >= 1 << (length - 1)) { return n; + } return n + (-1 << length) + 1; } @@ -162,8 +175,9 @@ var JpegImage = (function jpegImage() { var rs = decodeHuffman(component.huffmanTableAC); var s = rs & 15, r = rs >> 4; if (s === 0) { - if (r < 15) + if (r < 15) { break; + } k += 16; continue; } @@ -204,20 +218,26 @@ var JpegImage = (function jpegImage() { } k += r; var z = dctZigZag[k]; - component.blockData[offset + z] = receiveAndExtend(s) * (1 << successive); + component.blockData[offset + z] = + receiveAndExtend(s) * (1 << successive); k++; } } var successiveACState = 0, successiveACNextValue; function decodeACSuccessive(component, offset) { - var k = spectralStart, e = spectralEnd, r = 0; + var k = spectralStart; + var e = spectralEnd; + var r = 0; + var s; + var rs; while (k <= e) { var z = dctZigZag[k]; switch (successiveACState) { case 0: // initial state - var rs = decodeHuffman(component.huffmanTableAC); - var s = rs & 15, r = rs >> 4; + rs = decodeHuffman(component.huffmanTableAC); + s = rs & 15; + r = rs >> 4; if (s === 0) { if (r < 15) { eobrun = receive(r) + (1 << r); @@ -227,8 +247,9 @@ var JpegImage = (function jpegImage() { successiveACState = 1; } } else { - if (s !== 1) - throw "invalid ACn encoding"; + if (s !== 1) { + throw 'invalid ACn encoding'; + } successiveACNextValue = receiveAndExtend(s); successiveACState = r ? 2 : 3; } @@ -239,15 +260,17 @@ var JpegImage = (function jpegImage() { component.blockData[offset + z] += (readBit() << successive); } else { r--; - if (r === 0) + if (r === 0) { successiveACState = successiveACState == 2 ? 3 : 0; + } } break; case 3: // set value for a zero item if (component.blockData[offset + z]) { component.blockData[offset + z] += (readBit() << successive); } else { - component.blockData[offset + z] = successiveACNextValue << successive; + component.blockData[offset + z] = + successiveACNextValue << successive; successiveACState = 0; } break; @@ -261,8 +284,9 @@ var JpegImage = (function jpegImage() { } if (successiveACState === 4) { eobrun--; - if (eobrun === 0) + if (eobrun === 0) { successiveACState = 0; + } } } @@ -286,10 +310,11 @@ var JpegImage = (function jpegImage() { var component, i, j, k, n; var decodeFn; if (progressive) { - if (spectralStart === 0) + if (spectralStart === 0) { decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; - else + } else { decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; + } } else { decodeFn = decodeBaseline; } @@ -339,7 +364,7 @@ var JpegImage = (function jpegImage() { bitsCount = 0; marker = (data[offset] << 8) | data[offset + 1]; if (marker <= 0xFF00) { - throw "marker was not found"; + throw 'marker was not found'; } if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx @@ -354,7 +379,7 @@ var JpegImage = (function jpegImage() { // A port of poppler's IDCT method which in turn is taken from: // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, - // "Practical Fast 1-D DCT Algorithms with 11 Multiplications", + // 'Practical Fast 1-D DCT Algorithms with 11 Multiplications', // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, // 988-991. function quantizeAndInverse(component, blockBufferOffset, p) { @@ -372,9 +397,9 @@ var JpegImage = (function jpegImage() { var row = 8 * i; // check for all-zero AC coefficients - if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 && - p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 && - p[7 + row] == 0) { + if (p[1 + row] === 0 && p[2 + row] === 0 && p[3 + row] === 0 && + p[4 + row] === 0 && p[5 + row] === 0 && p[6 + row] === 0 && + p[7 + row] === 0) { t = (dctSqrt2 * p[0 + row] + 512) >> 10; p[0 + row] = t; p[1 + row] = t; @@ -441,9 +466,9 @@ var JpegImage = (function jpegImage() { var col = i; // check for all-zero AC coefficients - if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 && - p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 && - p[7*8 + col] == 0) { + if (p[1*8 + col] === 0 && p[2*8 + col] === 0 && p[3*8 + col] === 0 && + p[4*8 + col] === 0 && p[5*8 + col] === 0 && p[6*8 + col] === 0 && + p[7*8 + col] === 0) { t = (dctSqrt2 * p[i+0] + 8192) >> 14; p[0*8 + col] = t; p[1*8 + col] = t; @@ -508,7 +533,9 @@ var JpegImage = (function jpegImage() { // convert to 8-bit integers for (i = 0; i < 64; ++i) { var index = blockBufferOffset + i; - component.blockData[index] = clampTo8bitInt((p[i] + 2056) >> 4); + var q = p[i]; + q = (q <= -2056) ? 0 : (q >= 2024) ? 255 : (q + 2056) >> 4; + component.blockData[index] = q; } } @@ -522,36 +549,18 @@ var JpegImage = (function jpegImage() { var i, j, ll = 0; for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { - var offset = getBlockBufferOffset(component, blockRow, blockCol) + var offset = getBlockBufferOffset(component, blockRow, blockCol); quantizeAndInverse(component, offset, computationBuffer); } } return component.blockData; } - function clampTo8bitInt(a) { - return a <= 0 ? 0 : a >= 255 ? 255 : a | 0; - } - function clamp0to255(a) { return a <= 0 ? 0 : a >= 255 ? 255 : a; } constructor.prototype = { - load: function load(path) { - var xhr = new XMLHttpRequest(); - xhr.open("GET", path, true); - xhr.responseType = "arraybuffer"; - xhr.onload = (function() { - // TODO catch parse error - var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer); - this.parse(data); - if (this.onload) - this.onload(); - }).bind(this); - xhr.send(null); - }, - parse: function parse(data) { function readUint16() { @@ -572,13 +581,15 @@ var JpegImage = (function jpegImage() { var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); for (var i = 0; i < frame.components.length; i++) { component = frame.components[i]; - var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH); - var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV); + var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * + component.h / frame.maxH); + var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * + component.v / frame.maxV); var blocksPerLineForMcu = mcusPerLine * component.h; var blocksPerColumnForMcu = mcusPerColumn * component.v; - var blocksBufferSize = 64 * blocksPerColumnForMcu - * (blocksPerLineForMcu + 1); + var blocksBufferSize = 64 * blocksPerColumnForMcu * + (blocksPerLineForMcu + 1); component.blockData = new Int16Array(blocksBufferSize); component.blocksPerLine = blocksPerLine; component.blocksPerColumn = blocksPerColumn; @@ -596,7 +607,7 @@ var JpegImage = (function jpegImage() { var huffmanTablesAC = [], huffmanTablesDC = []; var fileMarker = readUint16(); if (fileMarker != 0xFFD8) { // SOI (Start of Image) - throw "SOI not found"; + throw 'SOI not found'; } fileMarker = readUint16(); @@ -623,8 +634,9 @@ var JpegImage = (function jpegImage() { var appData = readDataBlock(); if (fileMarker === 0xFFE0) { - if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 && - appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00' + if (appData[0] === 0x4A && appData[1] === 0x46 && + appData[2] === 0x49 && appData[3] === 0x46 && + appData[4] === 0) { // 'JFIF\x00' jfif = { version: { major: appData[5], minor: appData[6] }, densityUnits: appData[7], @@ -632,14 +644,16 @@ var JpegImage = (function jpegImage() { yDensity: (appData[10] << 8) | appData[11], thumbWidth: appData[12], thumbHeight: appData[13], - thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13]) + thumbData: appData.subarray(14, 14 + + 3 * appData[12] * appData[13]) }; } } // TODO APP1 - Exif if (fileMarker === 0xFFEE) { - if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && - appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00' + if (appData[0] === 0x41 && appData[1] === 0x64 && + appData[2] === 0x6F && appData[3] === 0x62 && + appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00' adobe = { version: appData[6], flags0: (appData[7] << 8) | appData[8], @@ -653,21 +667,23 @@ var JpegImage = (function jpegImage() { case 0xFFDB: // DQT (Define Quantization Tables) var quantizationTablesLength = readUint16(); var quantizationTablesEnd = quantizationTablesLength + offset - 2; + var z; while (offset < quantizationTablesEnd) { var quantizationTableSpec = data[offset++]; var tableData = new Int32Array(64); if ((quantizationTableSpec >> 4) === 0) { // 8 bit values for (j = 0; j < 64; j++) { - var z = dctZigZag[j]; + z = dctZigZag[j]; tableData[z] = data[offset++]; } } else if ((quantizationTableSpec >> 4) === 1) { //16 bit for (j = 0; j < 64; j++) { - var z = dctZigZag[j]; + z = dctZigZag[j]; tableData[z] = readUint16(); } - } else - throw "DQT: invalid table spec"; + } else { + throw 'DQT: invalid table spec'; + } quantizationTables[quantizationTableSpec & 15] = tableData; } break; @@ -676,7 +692,7 @@ var JpegImage = (function jpegImage() { case 0xFFC1: // SOF1 (Start of Frame, Extended DCT) case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT) if (frame) { - throw "Only single frame JPEGs supported"; + throw 'Only single frame JPEGs supported'; } readUint16(); // skip data length frame = {}; @@ -693,10 +709,14 @@ var JpegImage = (function jpegImage() { componentId = data[offset]; var h = data[offset + 1] >> 4; var v = data[offset + 1] & 15; - if (maxH < h) maxH = h; - if (maxV < v) maxV = v; + if (maxH < h) { + maxH = h; + } + if (maxV < v) { + maxV = v; + } var qId = data[offset + 2]; - var l = frame.components.push({ + l = frame.components.push({ h: h, v: v, quantizationTable: quantizationTables[qId] @@ -715,11 +735,13 @@ var JpegImage = (function jpegImage() { var huffmanTableSpec = data[offset++]; var codeLengths = new Uint8Array(16); var codeLengthSum = 0; - for (j = 0; j < 16; j++, offset++) + for (j = 0; j < 16; j++, offset++) { codeLengthSum += (codeLengths[j] = data[offset]); + } var huffmanValues = new Uint8Array(codeLengthSum); - for (j = 0; j < codeLengthSum; j++, offset++) + for (j = 0; j < codeLengthSum; j++, offset++) { huffmanValues[j] = data[offset]; + } i += 17 + codeLengthSum; ((huffmanTableSpec >> 4) === 0 ? @@ -762,7 +784,7 @@ var JpegImage = (function jpegImage() { offset -= 3; break; } - throw "unknown JPEG marker " + fileMarker.toString(16); + throw 'unknown JPEG marker ' + fileMarker.toString(16); } fileMarker = readUint16(); } @@ -772,8 +794,8 @@ var JpegImage = (function jpegImage() { this.jfif = jfif; this.adobe = adobe; this.components = []; - for (var i = 0; i < frame.components.length; i++) { - var component = frame.components[i]; + for (i = 0; i < frame.components.length; i++) { + component = frame.components[i]; this.components.push({ output: buildComponentData(frame, component), scaleX: component.h / frame.maxH, @@ -811,14 +833,17 @@ var JpegImage = (function jpegImage() { var samplesPerLine = blocksPerLine << 3; var j, k, ll = 0; + var sample; var lineOffset = 0; for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { var scanLine = blockRow << 3; for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { - var bufferOffset = getBlockBufferOffset(component, blockRow, blockCol); - var offset = 0, sample = blockCol << 3; + var bufferOffset = getBlockBufferOffset(component, + blockRow, blockCol); + offset = 0; + sample = blockCol << 3; for (j = 0; j < 8; j++) { - var lineOffset = (scanLine + j) * samplesPerLine; + lineOffset = (scanLine + j) * samplesPerLine; for (k = 0; k < 8; k++) { lineData[lineOffset + sample + k] = component.output[bufferOffset + offset++]; @@ -852,6 +877,8 @@ var JpegImage = (function jpegImage() { return true; } else if (this.numComponents == 3) { return true; + } else if (typeof this.colorTransform !== 'undefined') { + return !!this.colorTransform; } else { return false; } @@ -863,31 +890,65 @@ var JpegImage = (function jpegImage() { Y = data[i ]; Cb = data[i + 1]; Cr = data[i + 2]; - data[i ] = clamp0to255(Y + 1.402 * (Cr - 128)); - data[i + 1] = clamp0to255(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); - data[i + 2] = clamp0to255(Y + 1.772 * (Cb - 128)); + data[i ] = clamp0to255(Y - 179.456 + 1.402 * Cr); + data[i + 1] = clamp0to255(Y + 135.459 - 0.344 * Cb - 0.714 * Cr); + data[i + 2] = clamp0to255(Y - 226.816 + 1.772 * Cb); } return data; }, _convertYcckToRgb: function convertYcckToRgb(data) { - var Y, Cb, Cr, K, C, M, Ye, oneMinusK255th; - var outputData = new Uint8Array((data.length / 4) * 3); + var Y, Cb, Cr, k, CbCb, CbCr, CbY, Cbk, CrCr, Crk, CrY, YY, Yk, kk; var offset = 0; for (var i = 0; i < data.length; i += this.numComponents) { Y = data[i]; Cb = data[i + 1]; Cr = data[i + 2]; - K = data[i + 3]; - C = 255 - clamp0to255(Y + 1.402 * (Cr - 128)); - M = 255 - clamp0to255(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); - Ye = 255 - clamp0to255(Y + 1.772 * (Cb - 128)); - oneMinusK255th = (1 - K / 255); - outputData[offset++] = 255 - clamp0to255(C * oneMinusK255th + K); - outputData[offset++] = 255 - clamp0to255(M * oneMinusK255th + K); - outputData[offset++] = 255 - clamp0to255(Ye * oneMinusK255th + K); + k = data[i + 3]; + + CbCb = Cb * Cb; + CbCr = Cb * Cr; + CbY = Cb * Y; + Cbk = Cb * k; + CrCr = Cr * Cr; + Crk = Cr * k; + CrY = Cr * Y; + YY = Y * Y; + Yk = Y * k; + kk = k * k; + + var r = - 122.67195406894 - + 6.60635669420364e-5 * CbCb + 0.000437130475926232 * CbCr - + 5.4080610064599e-5* CbY + 0.00048449797120281* Cbk - + 0.154362151871126 * Cb - 0.000957964378445773 * CrCr + + 0.000817076911346625 * CrY - 0.00477271405408747 * Crk + + 1.53380253221734 * Cr + 0.000961250184130688 * YY - + 0.00266257332283933 * Yk + 0.48357088451265 * Y - + 0.000336197177618394 * kk + 0.484791561490776 * k; + + var g = 107.268039397724 + + 2.19927104525741e-5 * CbCb - 0.000640992018297945 * CbCr + + 0.000659397001245577* CbY + 0.000426105652938837* Cbk - + 0.176491792462875 * Cb - 0.000778269941513683 * CrCr + + 0.00130872261408275 * CrY + 0.000770482631801132 * Crk - + 0.151051492775562 * Cr + 0.00126935368114843 * YY - + 0.00265090189010898 * Yk + 0.25802910206845 * Y - + 0.000318913117588328 * kk - 0.213742400323665 * k; + + var b = - 20.810012546947 - + 0.000570115196973677 * CbCb - 2.63409051004589e-5 * CbCr + + 0.0020741088115012* CbY - 0.00288260236853442* Cbk + + 0.814272968359295 * Cb - 1.53496057440975e-5 * CrCr - + 0.000132689043961446 * CrY + 0.000560833691242812 * Crk - + 0.195152027534049 * Cr + 0.00174418132927582 * YY - + 0.00255243321439347 * Yk + 0.116935020465145 * Y - + 0.000343531996510555 * kk + 0.24165260232407 * k; + + data[offset++] = clamp0to255(r); + data[offset++] = clamp0to255(g); + data[offset++] = clamp0to255(b); } - return outputData; + return data; }, _convertYcckToCmyk: function convertYcckToCmyk(data) { @@ -896,30 +957,55 @@ var JpegImage = (function jpegImage() { Y = data[i]; Cb = data[i + 1]; Cr = data[i + 2]; - data[i ] = 255 - clamp0to255(Y + 1.402 * (Cr - 128)); - data[i + 1] = 255 - clamp0to255(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); - data[i + 2] = 255 - clamp0to255(Y + 1.772 * (Cb - 128)); + data[i ] = clamp0to255(434.456 - Y - 1.402 * Cr); + data[i + 1] = clamp0to255(119.541 - Y + 0.344 * Cb + 0.714 * Cr); + data[i + 2] = clamp0to255(481.816 - Y - 1.772 * Cb); // K in data[i + 3] is unchanged } return data; }, _convertCmykToRgb: function convertCmykToRgb(data) { - var C, M, Y, K, oneMinusK255th; - var outputData = new Uint8Array((data.length / 4) * 3); + var c, m, y, k; var offset = 0; for (var i = 0; i < data.length; i += this.numComponents) { - C = data[i ]; - M = data[i + 1]; - Y = data[i + 2]; - K = data[i + 3]; - oneMinusK255th = (1 - K / 255); - outputData[offset++] = 255 - clamp0to255(C * oneMinusK255th + K); - outputData[offset++] = 255 - clamp0to255(M * oneMinusK255th + K); - outputData[offset++] = 255 - clamp0to255(Y * oneMinusK255th + K); - // K in data[i + 3] is unchanged + c = data[i ] * 0.00392156862745098; + m = data[i + 1] * 0.00392156862745098; + y = data[i + 2] * 0.00392156862745098; + k = data[i + 3] * 0.00392156862745098; + + var r = + c * (-4.387332384609988 * c + 54.48615194189176 * m + + 18.82290502165302 * y + 212.25662451639585 * k + + -285.2331026137004) + + m * (1.7149763477362134 * m - 5.6096736904047315 * y + + -17.873870861415444 * k - 5.497006427196366) + + y * (-2.5217340131683033 * y - 21.248923337353073 * k + + 17.5119270841813) + + k * (-21.86122147463605 * k - 189.48180835922747) + 255; + var g = + c * (8.841041422036149 * c + 60.118027045597366 * m + + 6.871425592049007 * y + 31.159100130055922 * k + + -79.2970844816548) + + m * (-15.310361306967817 * m + 17.575251261109482 * y + + 131.35250912493976 * k - 190.9453302588951) + + y * (4.444339102852739 * y + 9.8632861493405 * k - 24.8674158255588) + + k * (-20.737325471181034 * k - 187.80453709719578) + 255; + var b = + c * (0.8842522430003296 * c + 8.078677503112928 * m + + 30.89978309703729 * y - 0.23883238689178934 * k + + -14.183576799673286) + + m * (10.49593273432072 * m + 63.02378494754052 * y + + 50.606957656360734 * k - 112.23884253719248) + + y * (0.03296041114873217 * y + 115.60384449646641 * k + + -193.58209356861505) + + k * (-22.33816807309886 * k - 180.12613974708367) + 255; + + data[offset++] = clamp0to255(r); + data[offset++] = clamp0to255(g); + data[offset++] = clamp0to255(b); } - return outputData; + return data; }, getData: function getData(width, height, forceRGBoutput) { @@ -946,39 +1032,6 @@ var JpegImage = (function jpegImage() { } } return data; - }, - - copyToImageData: function copyToImageData(imageData) { - var width = imageData.width; - var height = imageData.height; - var imageDataBytes = width * height * 4; - var imageDataArray = imageData.data; - var data = this.getData(width, height, /* forceRGBoutput = */true); - - var i = 0, j = 0; - switch (this.components.length) { - case 1: - var Y; - while (j < imageDataBytes) { - Y = data[i++]; - imageDataArray[j++] = Y; - imageDataArray[j++] = Y; - imageDataArray[j++] = Y; - imageDataArray[j++] = 255; - } - break; - case 3: - case 4: - while (j < imageDataBytes) { - imageDataArray[j++] = data[i++]; - imageDataArray[j++] = data[i++]; - imageDataArray[j++] = data[i++]; - imageDataArray[j++] = 255; - } - break; - default: - throw 'Unsupported color mode'; - } } }; diff --git a/src/core/stream.js b/src/core/stream.js index 57c39f316..abd9e81ed 100644 --- a/src/core/stream.js +++ b/src/core/stream.js @@ -873,9 +873,8 @@ var JpegStream = (function JpegStreamClosure() { jpegImage.colorTransform = this.colorTransform; } jpegImage.parse(this.bytes); - var width = jpegImage.width; - var height = jpegImage.height; - var data = jpegImage.getData(width, height); + var data = jpegImage.getData(this.drawWidth, this.drawHeight, + /* forceRGBoutput = */true); this.buffer = data; this.bufferLength = data.length; this.eof = true; @@ -883,6 +882,12 @@ var JpegStream = (function JpegStreamClosure() { error('JPEG error: ' + e); } }; + + JpegStream.prototype.getBytes = function JpegStream_getBytes(length) { + this.ensureBuffer(); + return this.buffer; + }; + JpegStream.prototype.getIR = function JpegStream_getIR() { return PDFJS.createObjectURL(this.bytes, 'image/jpeg'); }; diff --git a/src/worker_loader.js b/src/worker_loader.js index 7ee5e9d77..f48e26313 100644 --- a/src/worker_loader.js +++ b/src/worker_loader.js @@ -49,11 +49,11 @@ var otherFiles = [ 'core/stream.js', 'core/worker.js', 'core/arithmetic_decoder.js', + 'core/jpg.js', 'core/jpx.js', 'core/jbig2.js', 'core/bidi.js', - 'core/murmurhash3.js', - '../external/jpgjs/jpg.js' + 'core/murmurhash3.js' ]; function loadInOrder(index, path, files) { diff --git a/test/font/font_test.html b/test/font/font_test.html index b07044b87..70ca569e9 100644 --- a/test/font/font_test.html +++ b/test/font/font_test.html @@ -40,7 +40,7 @@ - + diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html index f56be2464..2a1562843 100644 --- a/test/unit/unit_test.html +++ b/test/unit/unit_test.html @@ -39,7 +39,7 @@ - +