From 1ccc8a64b7beeb79f5dc4aee1a03ef04f23997ee Mon Sep 17 00:00:00 2001 From: fkaelberer Date: Sat, 5 Apr 2014 17:27:18 +0200 Subject: [PATCH 1/2] Read color info from JPX stream Fix colors problem #4540 + minor cleanup fix lint warnings --- src/core/image.js | 38 ++++++++++++++------ src/core/jpx.js | 91 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 89 insertions(+), 40 deletions(-) diff --git a/src/core/image.js b/src/core/image.js index 8dc7b6dcb..17ea6b624 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, DecodeStream, error, isArray, ImageKind, isStream, - JpegStream, Name, Promise, Stream, warn, LegacyPromise */ +/* globals ColorSpace, DecodeStream, error, info, isArray, ImageKind, isStream, + JpegStream, JpxImage, Name, Promise, Stream, warn, LegacyPromise */ 'use strict'; @@ -51,16 +51,21 @@ var PDFImage = (function PDFImageClosure() { } function PDFImage(xref, res, image, inline, smask, mask, isMask) { this.image = image; - if (image.getParams) { - // JPX/JPEG2000 streams directly contain bits per component - // and color space mode information. - warn('get params from actual stream'); - // var bits = ... - // var colorspace = ... + var dict = image.dict; + if (dict.get('Filter').name === 'JPXDecode') { + info('get image params from JPX stream'); + var jpxImage = new JpxImage(); + var data = image.stream.bytes; + jpxImage.parseImageProperties(data, 0, data.length); + image.bitsPerComponent = jpxImage.bitsPerComponent; + image.numComps = jpxImage.componentsCount; + } + if (dict.get('Filter').name === 'JBIG2Decode') { + image.bitsPerComponent = 1; + image.numComps = 1; } // TODO cache rendered images? - var dict = image.dict; this.width = dict.get('Width', 'W'); this.height = dict.get('Height', 'H'); @@ -89,8 +94,19 @@ var PDFImage = (function PDFImageClosure() { if (!this.imageMask) { var colorSpace = dict.get('ColorSpace', 'CS'); if (!colorSpace) { - warn('JPX images (which do not require color spaces)'); - colorSpace = Name.get('DeviceRGB'); + info('JPX images (which do not require color spaces)'); + switch (image.numComps) { + case 1: + colorSpace = Name.get('DeviceGray'); + break; + case 3: + colorSpace = Name.get('DeviceRGB'); + break; + default: + // TODO: Find out how four color channels are handled. CMYK? Alpha? + error('JPX images with ' + this.numComps + + ' color components not supported.'); + } } this.colorSpace = ColorSpace.parse(colorSpace, xref, res); this.numComps = this.colorSpace.numComps; diff --git a/src/core/jpx.js b/src/core/jpx.js index 55d40783d..de8cc9011 100644 --- a/src/core/jpx.js +++ b/src/core/jpx.js @@ -26,10 +26,6 @@ var JpxImage = (function JpxImageClosure() { 'HL': 1, 'HH': 2 }; - var TransformType = { - IRREVERSIBLE: 0, - REVERSIBLE: 1 - }; function JpxImage() { this.failOnCorruptedImage = false; } @@ -102,6 +98,37 @@ var JpxImage = (function JpxImageClosure() { } } }, + parseImageProperties: function JpxImage_parseImageProperties(data, start, + end) { + try { + var position = start; + while (position + 40 < end) { + var code = readUint16(data, position); + // Image and tile size (SIZ) + if (code == 0xFF51) { + var Xsiz = readUint32(data, position + 6); + var Ysiz = readUint32(data, position + 10); + var XOsiz = readUint32(data, position + 14); + var YOsiz = readUint32(data, position + 18); + var Csiz = readUint16(data, position + 38); + this.width = Xsiz - XOsiz; + this.height = Ysiz - YOsiz; + this.componentsCount = Csiz; + // Results are always returned as UInt8Arrays + this.bitsPerComponent = 8; + return; + } + position += 1; + } + throw 'No size marker found in JPX stream'; + } catch (e) { + if (this.failOnCorruptedImage) { + error('JPX error: ' + e); + } else { + warn('JPX error: ' + e + '. Trying to recover'); + } + } + }, parseCodestream: function JpxImage_parseCodestream(data, start, end) { var context = {}; try { @@ -270,7 +297,7 @@ var JpxImage = (function JpxImageClosure() { cod.verticalyStripe = !!(blockStyle & 8); cod.predictableTermination = !!(blockStyle & 16); cod.segmentationSymbolUsed = !!(blockStyle & 32); - cod.transformation = data[j++]; + cod.reversibleTransformation = data[j++]; if (cod.entropyCoderWithCustomPrecincts) { var precinctsSizes = []; while (j < length + position) { @@ -333,6 +360,8 @@ var JpxImage = (function JpxImageClosure() { length = readUint16(data, position); // skipping content break; + case 0xFF53: // Coding style component (COC) + throw 'Codestream code 0xFF53 (COC) is not implemented'; default: throw 'Unknown codestream code: ' + code.toString(16); } @@ -878,7 +907,7 @@ var JpxImage = (function JpxImageClosure() { return position; } function copyCoefficients(coefficients, x0, y0, width, height, - delta, mb, codeblocks, transformation, + delta, mb, codeblocks, reversible, segmentationSymbolUsed) { for (var i = 0, ii = codeblocks.length; i < ii; ++i) { var codeblock = codeblocks[i]; @@ -934,16 +963,22 @@ var JpxImage = (function JpxImageClosure() { var offset = (codeblock.tbx0_ - x0) + (codeblock.tby0_ - y0) * width; var n, nb, correction, position = 0; - var irreversible = (transformation === TransformType.IRREVERSIBLE); + var irreversible = !reversible; var sign = bitModel.coefficentsSign; var magnitude = bitModel.coefficentsMagnitude; var bitsDecoded = bitModel.bitsDecoded; + var magnitudeCorrection = reversible ? 0 : 0.5; for (var j = 0; j < blockHeight; j++) { for (var k = 0; k < blockWidth; k++) { - n = (sign[position] ? -1 : 1) * magnitude[position]; - nb = bitsDecoded[position]; - correction = (irreversible || mb > nb) ? 1 << (mb - nb) : 1; - coefficients[offset++] = n * correction * delta; + var mag = magnitude[position]; + if (mag !== 0) { + n = sign[position] ? -(mag + magnitudeCorrection) : + (mag + magnitudeCorrection); + nb = bitsDecoded[position]; + correction = (irreversible || mb > nb) ? 1 << (mb - nb) : 1; + coefficients[offset] = n * correction * delta; + } + offset++; position++; } offset += width - blockWidth; @@ -959,16 +994,15 @@ var JpxImage = (function JpxImageClosure() { var spqcds = quantizationParameters.SPqcds; var scalarExpounded = quantizationParameters.scalarExpounded; var guardBits = quantizationParameters.guardBits; - var transformation = codingStyleParameters.transformation; var segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed; var precision = context.components[c].precision; - var transformation = codingStyleParameters.transformation; - var transform = (transformation === TransformType.IRREVERSIBLE ? - new IrreversibleTransform() : new ReversibleTransform()); + var reversible = codingStyleParameters.reversibleTransformation; + var transform = (reversible ? new ReversibleTransform() : + new IrreversibleTransform()); var subbandCoefficients = []; - var k = 0, b = 0; + var b = 0; for (var i = 0; i <= decompositionLevelsCount; i++) { var resolution = component.resolutions[i]; @@ -989,13 +1023,13 @@ var JpxImage = (function JpxImageClosure() { var gainLog2 = SubbandsGainLog2[subband.type]; // calulate quantization coefficient (Section E.1.1.1) - var delta = (transformation === TransformType.IRREVERSIBLE ? - Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048) : 1); + var delta = (reversible ? 1 : + Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048)); var mb = (guardBits + epsilon - 1); var coefficients = new Float32Array(width * height); copyCoefficients(coefficients, subband.tbx0, subband.tby0, - width, height, delta, mb, subband.codeblocks, transformation, + width, height, delta, mb, subband.codeblocks, reversible, segmentationSymbolUsed); subbandCoefficients.push({ @@ -1034,8 +1068,7 @@ var JpxImage = (function JpxImageClosure() { // Section G.2.2 Inverse multi component transform if (tile.codingStyleDefaultParameters.multipleComponentTransform) { var component0 = tile.components[0]; - var transformation = component0.codingStyleParameters.transformation; - if (transformation === TransformType.IRREVERSIBLE) { + if (!component0.codingStyleParameters.reversibleTransformation) { // inverse irreversible multiple component transform var y0items = result[0].items; var y1items = result[1].items; @@ -1628,26 +1661,26 @@ var JpxImage = (function JpxImageClosure() { var items = new Float32Array(width * height); var i, j, k, l; - for (i = 0; i < llHeight; i++) { - var k = i * llWidth, l = i * 2 * width; + for (i = 0, k = 0; i < llHeight; i++) { + l = i * 2 * width; for (var j = 0; j < llWidth; j++, k++, l += 2) { items[l] = llItems[k]; } } - for (i = 0; i < hlHeight; i++) { - k = i * hlWidth; l = i * 2 * width + 1; + for (i = 0, k = 0; i < hlHeight; i++) { + l = i * 2 * width + 1; for (j = 0; j < hlWidth; j++, k++, l += 2) { items[l] = hlItems[k]; } } - for (i = 0; i < lhHeight; i++) { - k = i * lhWidth; l = (i * 2 + 1) * width; + for (i = 0, k = 0; i < lhHeight; i++) { + l = (i * 2 + 1) * width; for (j = 0; j < lhWidth; j++, k++, l += 2) { items[l] = lhItems[k]; } } - for (i = 0; i < hhHeight; i++) { - k = i * hhWidth; l = (i * 2 + 1) * width + 1; + for (i = 0, k = 0; i < hhHeight; i++) { + l = (i * 2 + 1) * width + 1; for (j = 0; j < hhWidth; j++, k++, l += 2) { items[l] = hhItems[k]; } From 2982de8f33f1b785ff82c3dc46d12968286f3703 Mon Sep 17 00:00:00 2001 From: fkaelberer Date: Sun, 6 Apr 2014 12:08:04 +0200 Subject: [PATCH 2/2] Use Stream instead of byte array access --- src/core/image.js | 24 +++++++++++++----------- src/core/jpx.js | 24 +++++++++++++----------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/core/image.js b/src/core/image.js index 17ea6b624..edc8443dd 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -52,17 +52,19 @@ var PDFImage = (function PDFImageClosure() { function PDFImage(xref, res, image, inline, smask, mask, isMask) { this.image = image; var dict = image.dict; - if (dict.get('Filter').name === 'JPXDecode') { - info('get image params from JPX stream'); - var jpxImage = new JpxImage(); - var data = image.stream.bytes; - jpxImage.parseImageProperties(data, 0, data.length); - image.bitsPerComponent = jpxImage.bitsPerComponent; - image.numComps = jpxImage.componentsCount; - } - if (dict.get('Filter').name === 'JBIG2Decode') { - image.bitsPerComponent = 1; - image.numComps = 1; + if (dict.has('Filter')) { + var filter = dict.get('Filter').name; + if (filter === 'JPXDecode') { + info('get image params from JPX stream'); + var jpxImage = new JpxImage(); + jpxImage.parseImageProperties(image.stream); + image.stream.reset(); + image.bitsPerComponent = jpxImage.bitsPerComponent; + image.numComps = jpxImage.componentsCount; + } else if (filter === 'JBIG2Decode') { + image.bitsPerComponent = 1; + image.numComps = 1; + } } // TODO cache rendered images? diff --git a/src/core/jpx.js b/src/core/jpx.js index de8cc9011..8f484f1e7 100644 --- a/src/core/jpx.js +++ b/src/core/jpx.js @@ -98,19 +98,22 @@ var JpxImage = (function JpxImageClosure() { } } }, - parseImageProperties: function JpxImage_parseImageProperties(data, start, - end) { + parseImageProperties: function JpxImage_parseImageProperties(stream) { try { - var position = start; - while (position + 40 < end) { - var code = readUint16(data, position); + var newByte = stream.getByte(); + while (newByte >= 0) { + var oldByte = newByte; + newByte = stream.getByte(); + var code = (oldByte << 8) | newByte; // Image and tile size (SIZ) if (code == 0xFF51) { - var Xsiz = readUint32(data, position + 6); - var Ysiz = readUint32(data, position + 10); - var XOsiz = readUint32(data, position + 14); - var YOsiz = readUint32(data, position + 18); - var Csiz = readUint16(data, position + 38); + stream.skip(4); + var Xsiz = stream.getUint32(); // Byte 4 + var Ysiz = stream.getUint32(); // Byte 8 + var XOsiz = stream.getUint32(); // Byte 12 + var YOsiz = stream.getUint32(); // Byte 16 + stream.skip(16); + var Csiz = stream.getUint16(); // Byte 36 this.width = Xsiz - XOsiz; this.height = Ysiz - YOsiz; this.componentsCount = Csiz; @@ -118,7 +121,6 @@ var JpxImage = (function JpxImageClosure() { this.bitsPerComponent = 8; return; } - position += 1; } throw 'No size marker found in JPX stream'; } catch (e) {