From 116ba19dd90c56d729c0d1b163adcece9c291296 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 22 Sep 2016 14:07:20 +0200 Subject: [PATCH] Respect the 'ColorTransform' entry in the image dictionary when decoding JPEG images (bug 956965, issue 6574) Fixes https://bugzilla.mozilla.org/show_bug.cgi?id=956965. Fixes 6574. --- src/core/evaluator.js | 16 ++++++++++++---- src/core/jpg.js | 26 ++++++++++++++++++++------ src/core/parser.js | 2 +- src/core/stream.js | 17 +++++++++++++---- test/pdfs/bug956965.pdf.link | 1 + test/test_manifest.json | 9 +++++++++ 6 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 test/pdfs/bug956965.pdf.link diff --git a/src/core/evaluator.js b/src/core/evaluator.js index ba2ee9ef5..5a0450efe 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -146,18 +146,26 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { */ NativeImageDecoder.isSupported = function NativeImageDecoder_isSupported(image, xref, res) { - var cs = ColorSpace.parse(image.dict.get('ColorSpace', 'CS'), xref, res); + var dict = image.dict; + if (dict.has('DecodeParms') || dict.has('DP')) { + return false; + } + var cs = ColorSpace.parse(dict.get('ColorSpace', 'CS'), xref, res); return (cs.name === 'DeviceGray' || cs.name === 'DeviceRGB') && - cs.isDefaultDecode(image.dict.getArray('Decode', 'D')); + cs.isDefaultDecode(dict.getArray('Decode', 'D')); }; /** * Checks if the image can be decoded by the browser. */ NativeImageDecoder.isDecodable = function NativeImageDecoder_isDecodable(image, xref, res) { - var cs = ColorSpace.parse(image.dict.get('ColorSpace', 'CS'), xref, res); + var dict = image.dict; + if (dict.has('DecodeParms') || dict.has('DP')) { + return false; + } + var cs = ColorSpace.parse(dict.get('ColorSpace', 'CS'), xref, res); return (cs.numComps === 1 || cs.numComps === 3) && - cs.isDefaultDecode(image.dict.getArray('Decode', 'D')); + cs.isDefaultDecode(dict.getArray('Decode', 'D')); }; function PartialEvaluator(pdfManager, xref, handler, pageIndex, diff --git a/src/core/jpg.js b/src/core/jpg.js index a24f86ee5..e6bcdab2e 100644 --- a/src/core/jpg.js +++ b/src/core/jpg.js @@ -40,7 +40,7 @@ var error = sharedUtil.error; * (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) */ -var JpegImage = (function jpegImage() { +var JpegImage = (function JpegImageClosure() { var dctZigZag = new Uint8Array([ 0, 1, 8, @@ -68,7 +68,9 @@ var JpegImage = (function jpegImage() { var dctSqrt2 = 5793; // sqrt(2) var dctSqrt1d2 = 2896; // sqrt(2) / 2 - function constructor() { + function JpegImage() { + this.decodeTransform = null; + this.colorTransform = -1; } function buildHuffmanTable(codeLengths, values) { @@ -585,7 +587,7 @@ var JpegImage = (function jpegImage() { return a <= 0 ? 0 : a >= 255 ? 255 : a; } - constructor.prototype = { + JpegImage.prototype = { parse: function parse(data) { function readUint16() { @@ -902,8 +904,20 @@ var JpegImage = (function jpegImage() { // The adobe transform marker overrides any previous setting return true; } else if (this.numComponents === 3) { + if (!this.adobe && this.colorTransform === 0) { + // If the Adobe transform marker is not present and the image + // dictionary has a 'ColorTransform' entry, explicitly set to `0`, + // then the colours should *not* be transformed. + return false; + } return true; - } else { + } else { // `this.numComponents !== 3` + if (!this.adobe && this.colorTransform === 1) { + // If the Adobe transform marker is not present and the image + // dictionary has a 'ColorTransform' entry, explicitly set to `1`, + // then the colours should be transformed. + return true; + } return false; } }, @@ -1045,7 +1059,7 @@ var JpegImage = (function jpegImage() { rgbData[offset++] = grayColor; } return rgbData; - } else if (this.numComponents === 3) { + } else if (this.numComponents === 3 && this._isColorConversionNeeded()) { return this._convertYccToRgb(data); } else if (this.numComponents === 4) { if (this._isColorConversionNeeded()) { @@ -1062,7 +1076,7 @@ var JpegImage = (function jpegImage() { } }; - return constructor; + return JpegImage; })(); exports.JpegImage = JpegImage; diff --git a/src/core/parser.js b/src/core/parser.js index eeb42a00b..4436cc651 100644 --- a/src/core/parser.js +++ b/src/core/parser.js @@ -596,7 +596,7 @@ var Parser = (function ParserClosure() { } if (name === 'DCTDecode' || name === 'DCT') { xrefStreamStats[StreamType.DCT] = true; - return new JpegStream(stream, maybeLength, stream.dict, this.xref); + return new JpegStream(stream, maybeLength, stream.dict); } if (name === 'JPXDecode' || name === 'JPX') { xrefStreamStats[StreamType.JPX] = true; diff --git a/src/core/stream.js b/src/core/stream.js index 4f2051c48..1f569e90a 100644 --- a/src/core/stream.js +++ b/src/core/stream.js @@ -34,6 +34,7 @@ var Util = sharedUtil.Util; var error = sharedUtil.error; var info = sharedUtil.info; +var isInt = sharedUtil.isInt; var isArray = sharedUtil.isArray; var createObjectURL = sharedUtil.createObjectURL; var shadow = sharedUtil.shadow; @@ -891,7 +892,7 @@ var PredictorStream = (function PredictorStreamClosure() { * DecodeStreams. */ var JpegStream = (function JpegStreamClosure() { - function JpegStream(stream, maybeLength, dict, xref) { + function JpegStream(stream, maybeLength, dict) { // Some images may contain 'junk' before the SOI (start-of-image) marker. // Note: this seems to mainly affect inline images. var ch; @@ -925,8 +926,8 @@ var JpegStream = (function JpegStreamClosure() { var jpegImage = new JpegImage(); // Checking if values need to be transformed before conversion. - if (this.forceRGB && this.dict && isArray(this.dict.get('Decode'))) { - var decodeArr = this.dict.getArray('Decode'); + var decodeArr = this.dict.getArray('Decode', 'D'); + if (this.forceRGB && isArray(decodeArr)) { var bitsPerComponent = this.dict.get('BitsPerComponent') || 8; var decodeArrLength = decodeArr.length; var transform = new Int32Array(decodeArrLength); @@ -943,6 +944,14 @@ var JpegStream = (function JpegStreamClosure() { jpegImage.decodeTransform = transform; } } + // Fetching the 'ColorTransform' entry, if it exists. + var decodeParams = this.dict.get('DecodeParms', 'DP'); + if (isDict(decodeParams)) { + var colorTransform = decodeParams.get('ColorTransform'); + if (isInt(colorTransform)) { + jpegImage.colorTransform = colorTransform; + } + } jpegImage.parse(this.bytes); var data = jpegImage.getData(this.drawWidth, this.drawHeight, @@ -1064,7 +1073,7 @@ var Jbig2Stream = (function Jbig2StreamClosure() { var jbig2Image = new Jbig2Image(); var chunks = []; - var decodeParams = this.dict.getArray('DecodeParms'); + var decodeParams = this.dict.getArray('DecodeParms', 'DP'); // According to the PDF specification, DecodeParms can be either // a dictionary, or an array whose elements are dictionaries. diff --git a/test/pdfs/bug956965.pdf.link b/test/pdfs/bug956965.pdf.link new file mode 100644 index 000000000..f0b7a302b --- /dev/null +++ b/test/pdfs/bug956965.pdf.link @@ -0,0 +1 @@ +http://web.archive.org/web/20160414174617/http://cache.lego.com/bigdownloads/buildinginstructions/6030672.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index 531e054fb..d3b86503c 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -1156,6 +1156,15 @@ "lastPage": 1, "type": "eq" }, + { "id": "bug956965", + "file": "pdfs/bug956965.pdf", + "md5": "9b2f1176c797ee84e989a507e745f89d", + "rounds": 1, + "link": true, + "firstPage": 33, + "lastPage": 33, + "type": "eq" + }, { "id": "smaskdim", "file": "pdfs/smaskdim.pdf", "md5": "de80aeca7cbf79940189fd34d59671ee",