Merge pull request #4528 from bthorben/lessColorConversion

Less color conversion
This commit is contained in:
Yury Delendik 2014-04-28 08:28:33 -05:00
commit 58f697f977
7 changed files with 318 additions and 231 deletions

View File

@ -1,17 +0,0 @@
Copyright (C) 2011 notmasteryet
Contributors: Yury Delendik <ydelendik@mozilla.com>
Brendan Dahl <bdahl@mozilla.com>
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.

View File

@ -44,6 +44,7 @@ var PDFImage = (function PDFImageClosure() {
return Promise.resolve(image); return Promise.resolve(image);
} }
} }
/** /**
* Decode and clamp a value. The formula is different from the spec because we * 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. * 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 // Clamp the value to the range
return (value < 0 ? 0 : (value > max ? max : value)); return (value < 0 ? 0 : (value > max ? max : value));
} }
function PDFImage(xref, res, image, inline, smask, mask, isMask) { function PDFImage(xref, res, image, inline, smask, mask, isMask) {
this.image = image; this.image = image;
var dict = image.dict; var dict = image.dict;
@ -279,11 +281,13 @@ var PDFImage = (function PDFImageClosure() {
this.smask && this.smask.width || 0, this.smask && this.smask.width || 0,
this.mask && this.mask.width || 0); this.mask && this.mask.width || 0);
}, },
get drawHeight() { get drawHeight() {
return Math.max(this.height, return Math.max(this.height,
this.smask && this.smask.height || 0, this.smask && this.smask.height || 0,
this.mask && this.mask.height || 0); this.mask && this.mask.height || 0);
}, },
decodeBuffer: function PDFImage_decodeBuffer(buffer) { decodeBuffer: function PDFImage_decodeBuffer(buffer) {
var bpc = this.bpc; var bpc = this.bpc;
var numComps = this.numComps; var numComps = this.numComps;
@ -309,6 +313,7 @@ var PDFImage = (function PDFImageClosure() {
} }
} }
}, },
getComponents: function PDFImage_getComponents(buffer) { getComponents: function PDFImage_getComponents(buffer) {
var bpc = this.bpc; var bpc = this.bpc;
@ -385,6 +390,7 @@ var PDFImage = (function PDFImageClosure() {
} }
return output; return output;
}, },
fillOpacity: function PDFImage_fillOpacity(rgbaBuf, width, height, fillOpacity: function PDFImage_fillOpacity(rgbaBuf, width, height,
actualHeight, image) { actualHeight, image) {
var smask = this.smask; var smask = this.smask;
@ -451,6 +457,7 @@ var PDFImage = (function PDFImageClosure() {
} }
} }
}, },
undoPreblend: function PDFImage_undoPreblend(buffer, width, height) { undoPreblend: function PDFImage_undoPreblend(buffer, width, height) {
var matte = this.smask && this.smask.matte; var matte = this.smask && this.smask.matte;
if (!matte) { if (!matte) {
@ -467,7 +474,7 @@ var PDFImage = (function PDFImageClosure() {
var alpha = buffer[i + 3]; var alpha = buffer[i + 3];
if (alpha === 0) { if (alpha === 0) {
// according formula we have to get Infinity in all components // 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] = 255;
buffer[i + 1] = 255; buffer[i + 1] = 255;
buffer[i + 2] = 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]); buffer[i + 2] = clamp((buffer[i + 2] - matteRgb[2]) * k + matteRgb[2]);
} }
}, },
createImageData: function PDFImage_createImageData(forceRGBA) { createImageData: function PDFImage_createImageData(forceRGBA) {
var drawWidth = this.drawWidth; var drawWidth = this.drawWidth;
var drawHeight = this.drawHeight; var drawHeight = this.drawHeight;
@ -494,7 +502,7 @@ var PDFImage = (function PDFImageClosure() {
// Rows start at byte boundary. // Rows start at byte boundary.
var rowBytes = (originalWidth * numComps * bpc + 7) >> 3; var rowBytes = (originalWidth * numComps * bpc + 7) >> 3;
var imgArray = this.getImageBytes(originalHeight * rowBytes); var imgArray;
if (!forceRGBA) { if (!forceRGBA) {
// If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image // If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image
@ -514,6 +522,7 @@ var PDFImage = (function PDFImageClosure() {
drawWidth === originalWidth && drawHeight === originalHeight) { drawWidth === originalWidth && drawHeight === originalHeight) {
imgData.kind = kind; imgData.kind = kind;
imgArray = this.getImageBytes(originalHeight * rowBytes);
// If imgArray came from a DecodeStream, we're safe to transfer it // If imgArray came from a DecodeStream, we're safe to transfer it
// (and thus neuter it) because it will constitute the entire // (and thus neuter it) because it will constitute the entire
// DecodeStream's data. But if it came from a Stream, we need to // DecodeStream's data. But if it came from a Stream, we need to
@ -529,7 +538,14 @@ var PDFImage = (function PDFImageClosure() {
return imgData; 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). // imgArray can be incomplete (e.g. after CCITT fax encoding).
var actualHeight = 0 | (imgArray.length / rowBytes * var actualHeight = 0 | (imgArray.length / rowBytes *
drawHeight / originalHeight); drawHeight / originalHeight);
@ -567,6 +583,7 @@ var PDFImage = (function PDFImageClosure() {
return imgData; return imgData;
}, },
fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) { fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) {
var numComps = this.numComps; var numComps = this.numComps;
if (numComps != 1) { if (numComps != 1) {
@ -611,8 +628,12 @@ var PDFImage = (function PDFImageClosure() {
buffer[i] = (scale * comps[i]) | 0; buffer[i] = (scale * comps[i]) | 0;
} }
}, },
getImageBytes: function PDFImage_getImageBytes(length) {
getImageBytes: function PDFImage_getImageBytes(length,
drawWidth, drawHeight) {
this.image.reset(); this.image.reset();
this.image.drawWidth = drawWidth;
this.image.drawHeight = drawHeight;
return this.image.getBytes(length); return this.image.getBytes(length);
} }
}; };

View File

@ -1,31 +1,37 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* 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"); - The JPEG specification can be found in the ITU CCITT Recommendation T.81
you may not use this file except in compliance with the License. (www.w3.org/Graphics/JPEG/itu-t81.pdf)
You may obtain a copy of the License at - The JFIF specification can be found in the JPEG File Interchange Format
(www.w3.org/Graphics/JPEG/jfif3.pdf)
http://www.apache.org/licenses/LICENSE-2.0 - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters
in PostScript Level 2, Technical Note #5116
Unless required by applicable law or agreed to in writing, software (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)
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 'use strict';
// (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)
var JpegImage = (function jpegImage() { var JpegImage = (function jpegImage() {
"use strict";
var dctZigZag = new Int32Array([ var dctZigZag = new Int32Array([
0, 0,
1, 8, 1, 8,
@ -44,22 +50,23 @@ var JpegImage = (function jpegImage() {
63 63
]); ]);
var dctCos1 = 4017 // cos(pi/16) var dctCos1 = 4017; // cos(pi/16)
var dctSin1 = 799 // sin(pi/16) var dctSin1 = 799; // sin(pi/16)
var dctCos3 = 3406 // cos(3*pi/16) var dctCos3 = 3406; // cos(3*pi/16)
var dctSin3 = 2276 // sin(3*pi/16) var dctSin3 = 2276; // sin(3*pi/16)
var dctCos6 = 1567 // cos(6*pi/16) var dctCos6 = 1567; // cos(6*pi/16)
var dctSin6 = 3784 // sin(6*pi/16) var dctSin6 = 3784; // sin(6*pi/16)
var dctSqrt2 = 5793 // sqrt(2) var dctSqrt2 = 5793; // sqrt(2)
var dctSqrt1d2 = 2896 // sqrt(2) / 2 var dctSqrt1d2 = 2896; // sqrt(2) / 2
function constructor() { function constructor() {
} }
function buildHuffmanTable(codeLengths, values) { function buildHuffmanTable(codeLengths, values) {
var k = 0, code = [], i, j, length = 16; var k = 0, code = [], i, j, length = 16;
while (length > 0 && !codeLengths[length - 1]) while (length > 0 && !codeLengths[length - 1]) {
length--; length--;
}
code.push({children: [], index: 0}); code.push({children: [], index: 0});
var p = code[0], q; var p = code[0], q;
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
@ -114,7 +121,8 @@ var JpegImage = (function jpegImage() {
if (bitsData == 0xFF) { if (bitsData == 0xFF) {
var nextByte = data[offset++]; var nextByte = data[offset++];
if (nextByte) { if (nextByte) {
throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16); throw 'unexpected marker: ' +
((bitsData << 8) | nextByte).toString(16);
} }
// unstuff 0 // unstuff 0
} }
@ -127,10 +135,12 @@ var JpegImage = (function jpegImage() {
var bit; var bit;
while ((bit = readBit()) !== null) { while ((bit = readBit()) !== null) {
node = node[bit]; node = node[bit];
if (typeof node === 'number') if (typeof node === 'number') {
return node; return node;
if (typeof node !== 'object') }
throw "invalid huffman sequence"; if (typeof node !== 'object') {
throw 'invalid huffman sequence';
}
} }
return null; return null;
} }
@ -139,7 +149,9 @@ var JpegImage = (function jpegImage() {
var n = 0; var n = 0;
while (length > 0) { while (length > 0) {
var bit = readBit(); var bit = readBit();
if (bit === null) return; if (bit === null) {
return;
}
n = (n << 1) | bit; n = (n << 1) | bit;
length--; length--;
} }
@ -148,8 +160,9 @@ var JpegImage = (function jpegImage() {
function receiveAndExtend(length) { function receiveAndExtend(length) {
var n = receive(length); var n = receive(length);
if (n >= 1 << (length - 1)) if (n >= 1 << (length - 1)) {
return n; return n;
}
return n + (-1 << length) + 1; return n + (-1 << length) + 1;
} }
@ -162,8 +175,9 @@ var JpegImage = (function jpegImage() {
var rs = decodeHuffman(component.huffmanTableAC); var rs = decodeHuffman(component.huffmanTableAC);
var s = rs & 15, r = rs >> 4; var s = rs & 15, r = rs >> 4;
if (s === 0) { if (s === 0) {
if (r < 15) if (r < 15) {
break; break;
}
k += 16; k += 16;
continue; continue;
} }
@ -204,20 +218,26 @@ var JpegImage = (function jpegImage() {
} }
k += r; k += r;
var z = dctZigZag[k]; var z = dctZigZag[k];
component.blockData[offset + z] = receiveAndExtend(s) * (1 << successive); component.blockData[offset + z] =
receiveAndExtend(s) * (1 << successive);
k++; k++;
} }
} }
var successiveACState = 0, successiveACNextValue; var successiveACState = 0, successiveACNextValue;
function decodeACSuccessive(component, offset) { 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) { while (k <= e) {
var z = dctZigZag[k]; var z = dctZigZag[k];
switch (successiveACState) { switch (successiveACState) {
case 0: // initial state case 0: // initial state
var rs = decodeHuffman(component.huffmanTableAC); rs = decodeHuffman(component.huffmanTableAC);
var s = rs & 15, r = rs >> 4; s = rs & 15;
r = rs >> 4;
if (s === 0) { if (s === 0) {
if (r < 15) { if (r < 15) {
eobrun = receive(r) + (1 << r); eobrun = receive(r) + (1 << r);
@ -227,8 +247,9 @@ var JpegImage = (function jpegImage() {
successiveACState = 1; successiveACState = 1;
} }
} else { } else {
if (s !== 1) if (s !== 1) {
throw "invalid ACn encoding"; throw 'invalid ACn encoding';
}
successiveACNextValue = receiveAndExtend(s); successiveACNextValue = receiveAndExtend(s);
successiveACState = r ? 2 : 3; successiveACState = r ? 2 : 3;
} }
@ -239,15 +260,17 @@ var JpegImage = (function jpegImage() {
component.blockData[offset + z] += (readBit() << successive); component.blockData[offset + z] += (readBit() << successive);
} else { } else {
r--; r--;
if (r === 0) if (r === 0) {
successiveACState = successiveACState == 2 ? 3 : 0; successiveACState = successiveACState == 2 ? 3 : 0;
}
} }
break; break;
case 3: // set value for a zero item case 3: // set value for a zero item
if (component.blockData[offset + z]) { if (component.blockData[offset + z]) {
component.blockData[offset + z] += (readBit() << successive); component.blockData[offset + z] += (readBit() << successive);
} else { } else {
component.blockData[offset + z] = successiveACNextValue << successive; component.blockData[offset + z] =
successiveACNextValue << successive;
successiveACState = 0; successiveACState = 0;
} }
break; break;
@ -261,8 +284,9 @@ var JpegImage = (function jpegImage() {
} }
if (successiveACState === 4) { if (successiveACState === 4) {
eobrun--; eobrun--;
if (eobrun === 0) if (eobrun === 0) {
successiveACState = 0; successiveACState = 0;
}
} }
} }
@ -286,10 +310,11 @@ var JpegImage = (function jpegImage() {
var component, i, j, k, n; var component, i, j, k, n;
var decodeFn; var decodeFn;
if (progressive) { if (progressive) {
if (spectralStart === 0) if (spectralStart === 0) {
decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
else } else {
decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
}
} else { } else {
decodeFn = decodeBaseline; decodeFn = decodeBaseline;
} }
@ -339,7 +364,7 @@ var JpegImage = (function jpegImage() {
bitsCount = 0; bitsCount = 0;
marker = (data[offset] << 8) | data[offset + 1]; marker = (data[offset] << 8) | data[offset + 1];
if (marker <= 0xFF00) { if (marker <= 0xFF00) {
throw "marker was not found"; throw 'marker was not found';
} }
if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx 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: // A port of poppler's IDCT method which in turn is taken from:
// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, // 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, // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
// 988-991. // 988-991.
function quantizeAndInverse(component, blockBufferOffset, p) { function quantizeAndInverse(component, blockBufferOffset, p) {
@ -372,9 +397,9 @@ var JpegImage = (function jpegImage() {
var row = 8 * i; var row = 8 * i;
// check for all-zero AC coefficients // check for all-zero AC coefficients
if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + 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[4 + row] === 0 && p[5 + row] === 0 && p[6 + row] === 0 &&
p[7 + row] == 0) { p[7 + row] === 0) {
t = (dctSqrt2 * p[0 + row] + 512) >> 10; t = (dctSqrt2 * p[0 + row] + 512) >> 10;
p[0 + row] = t; p[0 + row] = t;
p[1 + row] = t; p[1 + row] = t;
@ -441,9 +466,9 @@ var JpegImage = (function jpegImage() {
var col = i; var col = i;
// check for all-zero AC coefficients // check for all-zero AC coefficients
if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*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[4*8 + col] === 0 && p[5*8 + col] === 0 && p[6*8 + col] === 0 &&
p[7*8 + col] == 0) { p[7*8 + col] === 0) {
t = (dctSqrt2 * p[i+0] + 8192) >> 14; t = (dctSqrt2 * p[i+0] + 8192) >> 14;
p[0*8 + col] = t; p[0*8 + col] = t;
p[1*8 + col] = t; p[1*8 + col] = t;
@ -508,7 +533,9 @@ var JpegImage = (function jpegImage() {
// convert to 8-bit integers // convert to 8-bit integers
for (i = 0; i < 64; ++i) { for (i = 0; i < 64; ++i) {
var index = blockBufferOffset + 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; var i, j, ll = 0;
for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
var offset = getBlockBufferOffset(component, blockRow, blockCol) var offset = getBlockBufferOffset(component, blockRow, blockCol);
quantizeAndInverse(component, offset, computationBuffer); quantizeAndInverse(component, offset, computationBuffer);
} }
} }
return component.blockData; return component.blockData;
} }
function clampTo8bitInt(a) {
return a <= 0 ? 0 : a >= 255 ? 255 : a | 0;
}
function clamp0to255(a) { function clamp0to255(a) {
return a <= 0 ? 0 : a >= 255 ? 255 : a; return a <= 0 ? 0 : a >= 255 ? 255 : a;
} }
constructor.prototype = { 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) { parse: function parse(data) {
function readUint16() { function readUint16() {
@ -572,13 +581,15 @@ var JpegImage = (function jpegImage() {
var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV);
for (var i = 0; i < frame.components.length; i++) { for (var i = 0; i < frame.components.length; i++) {
component = frame.components[i]; component = frame.components[i];
var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH); var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) *
var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV); component.h / frame.maxH);
var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) *
component.v / frame.maxV);
var blocksPerLineForMcu = mcusPerLine * component.h; var blocksPerLineForMcu = mcusPerLine * component.h;
var blocksPerColumnForMcu = mcusPerColumn * component.v; var blocksPerColumnForMcu = mcusPerColumn * component.v;
var blocksBufferSize = 64 * blocksPerColumnForMcu var blocksBufferSize = 64 * blocksPerColumnForMcu *
* (blocksPerLineForMcu + 1); (blocksPerLineForMcu + 1);
component.blockData = new Int16Array(blocksBufferSize); component.blockData = new Int16Array(blocksBufferSize);
component.blocksPerLine = blocksPerLine; component.blocksPerLine = blocksPerLine;
component.blocksPerColumn = blocksPerColumn; component.blocksPerColumn = blocksPerColumn;
@ -596,7 +607,7 @@ var JpegImage = (function jpegImage() {
var huffmanTablesAC = [], huffmanTablesDC = []; var huffmanTablesAC = [], huffmanTablesDC = [];
var fileMarker = readUint16(); var fileMarker = readUint16();
if (fileMarker != 0xFFD8) { // SOI (Start of Image) if (fileMarker != 0xFFD8) { // SOI (Start of Image)
throw "SOI not found"; throw 'SOI not found';
} }
fileMarker = readUint16(); fileMarker = readUint16();
@ -623,8 +634,9 @@ var JpegImage = (function jpegImage() {
var appData = readDataBlock(); var appData = readDataBlock();
if (fileMarker === 0xFFE0) { if (fileMarker === 0xFFE0) {
if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 && if (appData[0] === 0x4A && appData[1] === 0x46 &&
appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00' appData[2] === 0x49 && appData[3] === 0x46 &&
appData[4] === 0) { // 'JFIF\x00'
jfif = { jfif = {
version: { major: appData[5], minor: appData[6] }, version: { major: appData[5], minor: appData[6] },
densityUnits: appData[7], densityUnits: appData[7],
@ -632,14 +644,16 @@ var JpegImage = (function jpegImage() {
yDensity: (appData[10] << 8) | appData[11], yDensity: (appData[10] << 8) | appData[11],
thumbWidth: appData[12], thumbWidth: appData[12],
thumbHeight: appData[13], 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 // TODO APP1 - Exif
if (fileMarker === 0xFFEE) { if (fileMarker === 0xFFEE) {
if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && if (appData[0] === 0x41 && appData[1] === 0x64 &&
appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00' appData[2] === 0x6F && appData[3] === 0x62 &&
appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00'
adobe = { adobe = {
version: appData[6], version: appData[6],
flags0: (appData[7] << 8) | appData[8], flags0: (appData[7] << 8) | appData[8],
@ -653,21 +667,23 @@ var JpegImage = (function jpegImage() {
case 0xFFDB: // DQT (Define Quantization Tables) case 0xFFDB: // DQT (Define Quantization Tables)
var quantizationTablesLength = readUint16(); var quantizationTablesLength = readUint16();
var quantizationTablesEnd = quantizationTablesLength + offset - 2; var quantizationTablesEnd = quantizationTablesLength + offset - 2;
var z;
while (offset < quantizationTablesEnd) { while (offset < quantizationTablesEnd) {
var quantizationTableSpec = data[offset++]; var quantizationTableSpec = data[offset++];
var tableData = new Int32Array(64); var tableData = new Int32Array(64);
if ((quantizationTableSpec >> 4) === 0) { // 8 bit values if ((quantizationTableSpec >> 4) === 0) { // 8 bit values
for (j = 0; j < 64; j++) { for (j = 0; j < 64; j++) {
var z = dctZigZag[j]; z = dctZigZag[j];
tableData[z] = data[offset++]; tableData[z] = data[offset++];
} }
} else if ((quantizationTableSpec >> 4) === 1) { //16 bit } else if ((quantizationTableSpec >> 4) === 1) { //16 bit
for (j = 0; j < 64; j++) { for (j = 0; j < 64; j++) {
var z = dctZigZag[j]; z = dctZigZag[j];
tableData[z] = readUint16(); tableData[z] = readUint16();
} }
} else } else {
throw "DQT: invalid table spec"; throw 'DQT: invalid table spec';
}
quantizationTables[quantizationTableSpec & 15] = tableData; quantizationTables[quantizationTableSpec & 15] = tableData;
} }
break; break;
@ -676,7 +692,7 @@ var JpegImage = (function jpegImage() {
case 0xFFC1: // SOF1 (Start of Frame, Extended DCT) case 0xFFC1: // SOF1 (Start of Frame, Extended DCT)
case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT) case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT)
if (frame) { if (frame) {
throw "Only single frame JPEGs supported"; throw 'Only single frame JPEGs supported';
} }
readUint16(); // skip data length readUint16(); // skip data length
frame = {}; frame = {};
@ -693,10 +709,14 @@ var JpegImage = (function jpegImage() {
componentId = data[offset]; componentId = data[offset];
var h = data[offset + 1] >> 4; var h = data[offset + 1] >> 4;
var v = data[offset + 1] & 15; var v = data[offset + 1] & 15;
if (maxH < h) maxH = h; if (maxH < h) {
if (maxV < v) maxV = v; maxH = h;
}
if (maxV < v) {
maxV = v;
}
var qId = data[offset + 2]; var qId = data[offset + 2];
var l = frame.components.push({ l = frame.components.push({
h: h, h: h,
v: v, v: v,
quantizationTable: quantizationTables[qId] quantizationTable: quantizationTables[qId]
@ -715,11 +735,13 @@ var JpegImage = (function jpegImage() {
var huffmanTableSpec = data[offset++]; var huffmanTableSpec = data[offset++];
var codeLengths = new Uint8Array(16); var codeLengths = new Uint8Array(16);
var codeLengthSum = 0; var codeLengthSum = 0;
for (j = 0; j < 16; j++, offset++) for (j = 0; j < 16; j++, offset++) {
codeLengthSum += (codeLengths[j] = data[offset]); codeLengthSum += (codeLengths[j] = data[offset]);
}
var huffmanValues = new Uint8Array(codeLengthSum); var huffmanValues = new Uint8Array(codeLengthSum);
for (j = 0; j < codeLengthSum; j++, offset++) for (j = 0; j < codeLengthSum; j++, offset++) {
huffmanValues[j] = data[offset]; huffmanValues[j] = data[offset];
}
i += 17 + codeLengthSum; i += 17 + codeLengthSum;
((huffmanTableSpec >> 4) === 0 ? ((huffmanTableSpec >> 4) === 0 ?
@ -762,7 +784,7 @@ var JpegImage = (function jpegImage() {
offset -= 3; offset -= 3;
break; break;
} }
throw "unknown JPEG marker " + fileMarker.toString(16); throw 'unknown JPEG marker ' + fileMarker.toString(16);
} }
fileMarker = readUint16(); fileMarker = readUint16();
} }
@ -772,8 +794,8 @@ var JpegImage = (function jpegImage() {
this.jfif = jfif; this.jfif = jfif;
this.adobe = adobe; this.adobe = adobe;
this.components = []; this.components = [];
for (var i = 0; i < frame.components.length; i++) { for (i = 0; i < frame.components.length; i++) {
var component = frame.components[i]; component = frame.components[i];
this.components.push({ this.components.push({
output: buildComponentData(frame, component), output: buildComponentData(frame, component),
scaleX: component.h / frame.maxH, scaleX: component.h / frame.maxH,
@ -782,9 +804,10 @@ var JpegImage = (function jpegImage() {
blocksPerColumn: component.blocksPerColumn 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 scaleX = this.width / width, scaleY = this.height / height;
var component, componentScaleX, componentScaleY; var component, componentScaleX, componentScaleY;
@ -810,14 +833,17 @@ var JpegImage = (function jpegImage() {
var samplesPerLine = blocksPerLine << 3; var samplesPerLine = blocksPerLine << 3;
var j, k, ll = 0; var j, k, ll = 0;
var sample;
var lineOffset = 0; var lineOffset = 0;
for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
var scanLine = blockRow << 3; var scanLine = blockRow << 3;
for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
var bufferOffset = getBlockBufferOffset(component, blockRow, blockCol); var bufferOffset = getBlockBufferOffset(component,
var offset = 0, sample = blockCol << 3; blockRow, blockCol);
offset = 0;
sample = blockCol << 3;
for (j = 0; j < 8; j++) { for (j = 0; j < 8; j++) {
var lineOffset = (scanLine + j) * samplesPerLine; lineOffset = (scanLine + j) * samplesPerLine;
for (k = 0; k < 8; k++) { for (k = 0; k < 8; k++) {
lineData[lineOffset + sample + k] = lineData[lineOffset + sample + k] =
component.output[bufferOffset + offset++]; component.output[bufferOffset + offset++];
@ -842,118 +868,170 @@ var JpegImage = (function jpegImage() {
} }
} }
} }
return data;
},
// ... then transform colors, if necessary _isColorConversionNeeded: function isColorConversionNeeded() {
switch (numComponents) { if (this.adobe && this.adobe.transformCode) {
case 1: case 2: break; // The adobe transform marker overrides any previous setting
// no color conversion for one or two compoenents return true;
} else if (this.numComponents == 3) {
return true;
} else if (typeof this.colorTransform !== 'undefined') {
return !!this.colorTransform;
} else {
return false;
}
},
case 3: _convertYccToRgb: function convertYccToRgb(data) {
// The default transform for three components is true var Y, Cb, Cr;
colorTransform = true; for (var i = 0; i < data.length; i += this.numComponents) {
// The adobe transform marker overrides any previous setting Y = data[i ];
if (this.adobe && this.adobe.transformCode) Cb = data[i + 1];
colorTransform = true; Cr = data[i + 2];
else if (typeof this.colorTransform !== 'undefined') data[i ] = clamp0to255(Y - 179.456 + 1.402 * Cr);
colorTransform = !!this.colorTransform; data[i + 1] = clamp0to255(Y + 135.459 - 0.344 * Cb - 0.714 * Cr);
data[i + 2] = clamp0to255(Y - 226.816 + 1.772 * Cb);
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';
} }
return data; return data;
}, },
copyToImageData: function copyToImageData(imageData) {
var width = imageData.width, height = imageData.height;
var imageDataBytes = width * height * 4;
var imageDataArray = imageData.data;
var data = this.getData(width, height);
var i = 0, j = 0;
var Y, K, C, M, R, G, B;
switch (this.components.length) {
case 1:
while (j < imageDataBytes) {
Y = data[i++];
imageDataArray[j++] = Y; _convertYcckToRgb: function convertYcckToRgb(data) {
imageDataArray[j++] = Y; var Y, Cb, Cr, k, CbCb, CbCr, CbY, Cbk, CrCr, Crk, CrY, YY, Yk, kk;
imageDataArray[j++] = Y; var offset = 0;
imageDataArray[j++] = 255; for (var i = 0; i < data.length; i += this.numComponents) {
} Y = data[i];
break; Cb = data[i + 1];
case 3: Cr = data[i + 2];
while (j < imageDataBytes) { k = data[i + 3];
R = data[i++];
G = data[i++];
B = data[i++];
imageDataArray[j++] = R; CbCb = Cb * Cb;
imageDataArray[j++] = G; CbCr = Cb * Cr;
imageDataArray[j++] = B; CbY = Cb * Y;
imageDataArray[j++] = 255; Cbk = Cb * k;
} CrCr = Cr * Cr;
break; Crk = Cr * k;
case 4: CrY = Cr * Y;
while (j < imageDataBytes) { YY = Y * Y;
C = data[i++]; Yk = Y * k;
M = data[i++]; kk = k * k;
Y = data[i++];
K = data[i++];
R = 255 - clamp0to255(C * (1 - K / 255) + K); var r = - 122.67195406894 -
G = 255 - clamp0to255(M * (1 - K / 255) + K); 6.60635669420364e-5 * CbCb + 0.000437130475926232 * CbCr -
B = 255 - clamp0to255(Y * (1 - K / 255) + K); 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;
imageDataArray[j++] = R; var g = 107.268039397724 +
imageDataArray[j++] = G; 2.19927104525741e-5 * CbCb - 0.000640992018297945 * CbCr +
imageDataArray[j++] = B; 0.000659397001245577* CbY + 0.000426105652938837* Cbk -
imageDataArray[j++] = 255; 0.176491792462875 * Cb - 0.000778269941513683 * CrCr +
} 0.00130872261408275 * CrY + 0.000770482631801132 * Crk -
break; 0.151051492775562 * Cr + 0.00126935368114843 * YY -
default: 0.00265090189010898 * Yk + 0.25802910206845 * Y -
throw 'Unsupported color mode'; 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 data;
},
_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 ] = 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;
var offset = 0;
for (var i = 0; i < data.length; i += this.numComponents) {
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 data;
},
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;
} }
}; };

View File

@ -873,9 +873,8 @@ var JpegStream = (function JpegStreamClosure() {
jpegImage.colorTransform = this.colorTransform; jpegImage.colorTransform = this.colorTransform;
} }
jpegImage.parse(this.bytes); jpegImage.parse(this.bytes);
var width = jpegImage.width; var data = jpegImage.getData(this.drawWidth, this.drawHeight,
var height = jpegImage.height; /* forceRGBoutput = */true);
var data = jpegImage.getData(width, height);
this.buffer = data; this.buffer = data;
this.bufferLength = data.length; this.bufferLength = data.length;
this.eof = true; this.eof = true;
@ -883,6 +882,12 @@ var JpegStream = (function JpegStreamClosure() {
error('JPEG error: ' + e); error('JPEG error: ' + e);
} }
}; };
JpegStream.prototype.getBytes = function JpegStream_getBytes(length) {
this.ensureBuffer();
return this.buffer;
};
JpegStream.prototype.getIR = function JpegStream_getIR() { JpegStream.prototype.getIR = function JpegStream_getIR() {
return PDFJS.createObjectURL(this.bytes, 'image/jpeg'); return PDFJS.createObjectURL(this.bytes, 'image/jpeg');
}; };

View File

@ -49,11 +49,11 @@ var otherFiles = [
'core/stream.js', 'core/stream.js',
'core/worker.js', 'core/worker.js',
'core/arithmetic_decoder.js', 'core/arithmetic_decoder.js',
'core/jpg.js',
'core/jpx.js', 'core/jpx.js',
'core/jbig2.js', 'core/jbig2.js',
'core/bidi.js', 'core/bidi.js',
'core/murmurhash3.js', 'core/murmurhash3.js'
'../external/jpgjs/jpg.js'
]; ];
function loadInOrder(index, path, files) { function loadInOrder(index, path, files) {

View File

@ -40,7 +40,7 @@
<script type="text/javascript" src="../../src/core/stream.js"></script> <script type="text/javascript" src="../../src/core/stream.js"></script>
<script type="text/javascript" src="../../src/core/worker.js"></script> <script type="text/javascript" src="../../src/core/worker.js"></script>
<script type="text/javascript" src="../../src/display/metadata.js"></script> <script type="text/javascript" src="../../src/display/metadata.js"></script>
<script type="text/javascript" src="../../external/jpgjs/jpg.js"></script> <script type="text/javascript" src="../../src/core/jpg.js"></script>
<script type="text/javascript">PDFJS.workerSrc = '../../src/worker_loader.js';</script> <script type="text/javascript">PDFJS.workerSrc = '../../src/worker_loader.js';</script>
<!-- include spec files here... --> <!-- include spec files here... -->

View File

@ -39,7 +39,7 @@
<script type="text/javascript" src="../../src/core/stream.js"></script> <script type="text/javascript" src="../../src/core/stream.js"></script>
<script type="text/javascript" src="../../src/core/worker.js"></script> <script type="text/javascript" src="../../src/core/worker.js"></script>
<script type="text/javascript" src="../../src/display/metadata.js"></script> <script type="text/javascript" src="../../src/display/metadata.js"></script>
<script type="text/javascript" src="../../external/jpgjs/jpg.js"></script> <script type="text/javascript" src="../../src/core/jpg.js"></script>
<script type="text/javascript">PDFJS.workerSrc = '../../src/worker_loader.js';</script> <script type="text/javascript">PDFJS.workerSrc = '../../src/worker_loader.js';</script>
<!-- include spec files here... --> <!-- include spec files here... -->