From 09b52d766361ae3ab931f63d8c77c5ef9e118fbf Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 3 Apr 2013 10:36:09 -0700 Subject: [PATCH] Fix lab colorspace decoding and rgb conversion. --- src/colorspace.js | 82 +++++++++++++++++++++++++++------------- test/pdfs/issue2761.pdf | Bin 0 -> 12142 bytes test/test_manifest.json | 9 ++++- 3 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 test/pdfs/issue2761.pdf diff --git a/src/colorspace.js b/src/colorspace.js index d55fd719e..957bdd218 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -105,7 +105,13 @@ var ColorSpace = (function ColorSpaceClosure() { } this.getRgbBuffer(src, srcOffset, count, dest, 0, bits); return dest; - } + }, + /** + * True if the colorspace has components in the default range of [0, 1]. + * This should be true for all colorspaces except for lab color spaces + * which are [0,100], [-128, 127], [-128, 127]. + */ + usesZeroToOneRange: true }; ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { @@ -317,8 +323,8 @@ var AlternateCS = (function AlternateCSClosure() { var base = this.base; var scale = 1 / ((1 << bits) - 1); var baseNumComps = base.numComps; - var isGetRgbBufferSupported = 'getRgbBuffer' in base; - var isPassthrough = base.isPassthrough(8) || !isGetRgbBufferSupported; + var usesZeroToOneRange = base.usesZeroToOneRange; + var isPassthrough = base.isPassthrough(8) || !usesZeroToOneRange; var pos = isPassthrough ? destOffset : 0; var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count); var numComps = this.numComps; @@ -329,7 +335,7 @@ var AlternateCS = (function AlternateCSClosure() { scaled[j] = src[srcOffset++] * scale; } var tinted = tintFn(scaled); - if (isGetRgbBufferSupported) { + if (usesZeroToOneRange) { for (var j = 0; j < baseNumComps; j++) { baseBuf[pos++] = tinted[j] * 255; } @@ -350,7 +356,8 @@ var AlternateCS = (function AlternateCSClosure() { createRgbBuffer: ColorSpace.prototype.createRgbBuffer, isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) { return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - } + }, + usesZeroToOneRange: true }; return AlternateCS; @@ -427,7 +434,8 @@ var IndexedCS = (function IndexedCSClosure() { isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) { // indexed color maps shouldn't be changed return true; - } + }, + usesZeroToOneRange: true }; return IndexedCS; })(); @@ -469,7 +477,8 @@ var DeviceGrayCS = (function DeviceGrayCSClosure() { createRgbBuffer: ColorSpace.prototype.createRgbBuffer, isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) { return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - } + }, + usesZeroToOneRange: true }; return DeviceGrayCS; })(); @@ -517,7 +526,8 @@ var DeviceRgbCS = (function DeviceRgbCSClosure() { createRgbBuffer: ColorSpace.prototype.createRgbBuffer, isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) { return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - } + }, + usesZeroToOneRange: true }; return DeviceRgbCS; })(); @@ -1266,7 +1276,8 @@ var DeviceCmykCS = (function DeviceCmykCSClosure() { createRgbBuffer: ColorSpace.prototype.createRgbBuffer, isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) { return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - } + }, + usesZeroToOneRange: true }; return DeviceCmykCS; @@ -1327,11 +1338,26 @@ var LabCS = (function LabCSClosure() { return (108 / 841) * (x - 4 / 29); } - function convertToRgb(cs, src, srcOffset, dest, destOffset) { + function decode(value, high1, low2, high2) { + return low2 + (value) * (high2 - low2) / (high1); + } + + // If decoding is needed maxVal should be 2^bits per component - 1. + function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) { + // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax] + // not the usual [0, 1]. If a command like setFillColor is used the src + // values will already be within the correct range. However, if we are + // converting an image we have to map the values to the correct range given + // above. // Ls,as,bs <---> L*,a*,b* in the spec var Ls = src[srcOffset]; var as = src[srcOffset + 1]; var bs = src[srcOffset + 2]; + if (maxVal !== false) { + Ls = decode(Ls, maxVal, 0, 100); + as = decode(as, maxVal, cs.amin, cs.amax); + bs = decode(bs, maxVal, cs.bmin, cs.bmax); + } // Adjust limits of 'as' and 'bs' as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as; @@ -1360,36 +1386,40 @@ var LabCS = (function LabCSClosure() { g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; b = X * 0.0557 + Y * -0.2040 + Z * 1.0570; } - - // clamp color values to [0,255] range - dest[destOffset] = r < 0 ? 0 : r > 1 ? 255 : r * 255; - dest[destOffset + 1] = g < 0 ? 0 : g > 1 ? 255 : g * 255; - dest[destOffset + 2] = b < 0 ? 0 : b > 1 ? 255 : b * 255; + // clamp color values to [0,1] range then convert to [0,255] range. + dest[destOffset] = Math.sqrt(r < 0 ? 0 : r > 1 ? 1 : r) * 255; + dest[destOffset + 1] = Math.sqrt(g < 0 ? 0 : g > 1 ? 1 : g) * 255; + dest[destOffset + 2] = Math.sqrt(b < 0 ? 0 : b > 1 ? 1 : b) * 255; } LabCS.prototype = { getRgb: function LabCS_getRgb(src, srcOffset) { var rgb = new Uint8Array(3); - convertToRgb(this, src, srcOffset, rgb, 0); + convertToRgb(this, src, srcOffset, false, rgb, 0); return rgb; }, getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) { - convertToRgb(this, src, srcOffset, dest, destOffset); + convertToRgb(this, src, srcOffset, false, dest, destOffset); + }, + getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var maxVal = (1 << bits) - 1; + for (var i = 0; i < count; i++) { + convertToRgb(this, src, srcOffset, maxVal, dest, destOffset); + srcOffset += 3; + destOffset += 3; + } }, getOutputLength: function LabCS_getOutputLength(inputLength) { return inputLength; }, isPassthrough: ColorSpace.prototype.isPassthrough, isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) { - // From Table 90 in Adobe's: - // "Document management - Portable document format", 1st ed, 2008 - if (decodeMap[0] === 0 && decodeMap[1] === 100 && - decodeMap[2] === this.amin && decodeMap[3] === this.amax && - decodeMap[4] === this.bmin && decodeMap[5] === this.bmax) - return true; - else - return false; - } + // XXX: Decoding is handled with the lab conversion because of the strange + // ranges that are used. + return true; + }, + usesZeroToOneRange: false }; return LabCS; })(); diff --git a/test/pdfs/issue2761.pdf b/test/pdfs/issue2761.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a0b3ba93bbf594a9b815a379dd5b5d95d98594ae GIT binary patch literal 12142 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 D+dm9k literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index 875ab5b05..a70942682 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -872,7 +872,7 @@ "rounds": 1, "lastPage": 2, "link": true, - "type": "load" + "type": "eq" }, { "id": "issue2627", "file": "pdfs/issue2627.pdf", @@ -1037,6 +1037,13 @@ "rounds": 1, "type": "eq" }, + { "id": "issue2761", + "file": "pdfs/issue2761.pdf", + "md5": "35df0b8cff4afec0c08f08c6a5bc9857", + "rounds": 1, + "type": "eq", + "about": "Image with indexed colorspace that has a base lab colorspace." + }, { "id": "yo01", "file": "pdfs/yo01.pdf", "md5": "7d42435c20fe0d32de4ea3d7e4727ac1",