You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
450 lines
13 KiB
450 lines
13 KiB
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
|
|
|
'use strict'; |
|
|
|
var ColorSpace = (function ColorSpaceClosure() { |
|
// Constructor should define this.numComps, this.defaultColor, this.name |
|
function ColorSpace() { |
|
error('should not call ColorSpace constructor'); |
|
} |
|
|
|
ColorSpace.prototype = { |
|
// Input: array of size numComps representing color component values |
|
// Output: array of rgb values, each value ranging from [0.1] |
|
getRgb: function colorSpaceGetRgb(color) { |
|
error('Should not call ColorSpace.getRgb: ' + color); |
|
}, |
|
// Input: Uint8Array of component values, each value scaled to [0,255] |
|
// Output: Uint8Array of rgb values, each value scaled to [0,255] |
|
getRgbBuffer: function colorSpaceGetRgbBuffer(input) { |
|
error('Should not call ColorSpace.getRgbBuffer: ' + input); |
|
} |
|
}; |
|
|
|
ColorSpace.parse = function colorSpaceParse(cs, xref, res) { |
|
var IR = ColorSpace.parseToIR(cs, xref, res); |
|
if (IR instanceof AlternateCS) |
|
return IR; |
|
|
|
return ColorSpace.fromIR(IR); |
|
}; |
|
|
|
ColorSpace.fromIR = function colorSpaceFromIR(IR) { |
|
var name = isArray(IR) ? IR[0] : IR; |
|
|
|
switch (name) { |
|
case 'DeviceGrayCS': |
|
return new DeviceGrayCS(); |
|
case 'DeviceRgbCS': |
|
return new DeviceRgbCS(); |
|
case 'DeviceCmykCS': |
|
return new DeviceCmykCS(); |
|
case 'PatternCS': |
|
var basePatternCS = IR[1]; |
|
if (basePatternCS) |
|
basePatternCS = ColorSpace.fromIR(basePatternCS); |
|
return new PatternCS(basePatternCS); |
|
case 'IndexedCS': |
|
var baseIndexedCS = IR[1]; |
|
var hiVal = IR[2]; |
|
var lookup = IR[3]; |
|
return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); |
|
case 'AlternateCS': |
|
var numComps = IR[1]; |
|
var alt = IR[2]; |
|
var tintFnIR = IR[3]; |
|
|
|
return new AlternateCS(numComps, ColorSpace.fromIR(alt), |
|
PDFFunction.fromIR(tintFnIR)); |
|
default: |
|
error('Unkown name ' + name); |
|
} |
|
return null; |
|
}; |
|
|
|
ColorSpace.parseToIR = function colorSpaceParseToIR(cs, xref, res) { |
|
if (isName(cs)) { |
|
var colorSpaces = xref.fetchIfRef(res.get('ColorSpace')); |
|
if (isDict(colorSpaces)) { |
|
var refcs = colorSpaces.get(cs.name); |
|
if (refcs) |
|
cs = refcs; |
|
} |
|
} |
|
|
|
cs = xref.fetchIfRef(cs); |
|
var mode; |
|
|
|
if (isName(cs)) { |
|
mode = cs.name; |
|
this.mode = mode; |
|
|
|
switch (mode) { |
|
case 'DeviceGray': |
|
case 'G': |
|
return 'DeviceGrayCS'; |
|
case 'DeviceRGB': |
|
case 'RGB': |
|
return 'DeviceRgbCS'; |
|
case 'DeviceCMYK': |
|
case 'CMYK': |
|
return 'DeviceCmykCS'; |
|
case 'Pattern': |
|
return ['PatternCS', null]; |
|
default: |
|
error('unrecognized colorspace ' + mode); |
|
} |
|
} else if (isArray(cs)) { |
|
mode = cs[0].name; |
|
this.mode = mode; |
|
|
|
switch (mode) { |
|
case 'DeviceGray': |
|
case 'G': |
|
return 'DeviceGrayCS'; |
|
case 'DeviceRGB': |
|
case 'RGB': |
|
return 'DeviceRgbCS'; |
|
case 'DeviceCMYK': |
|
case 'CMYK': |
|
return 'DeviceCmykCS'; |
|
case 'CalGray': |
|
return 'DeviceGrayCS'; |
|
case 'CalRGB': |
|
return 'DeviceRgbCS'; |
|
case 'ICCBased': |
|
var stream = xref.fetchIfRef(cs[1]); |
|
var dict = stream.dict; |
|
var numComps = dict.get('N'); |
|
if (numComps == 1) |
|
return 'DeviceGrayCS'; |
|
if (numComps == 3) |
|
return 'DeviceRgbCS'; |
|
if (numComps == 4) |
|
return 'DeviceCmykCS'; |
|
break; |
|
case 'Pattern': |
|
var basePatternCS = cs[1]; |
|
if (basePatternCS) |
|
basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); |
|
return ['PatternCS', basePatternCS]; |
|
case 'Indexed': |
|
var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res); |
|
var hiVal = cs[2] + 1; |
|
var lookup = xref.fetchIfRef(cs[3]); |
|
return ['IndexedCS', baseIndexedCS, hiVal, lookup]; |
|
case 'Separation': |
|
case 'DeviceN': |
|
var name = cs[1]; |
|
var numComps = 1; |
|
if (isName(name)) |
|
numComps = 1; |
|
else if (isArray(name)) |
|
numComps = name.length; |
|
var alt = ColorSpace.parseToIR(cs[2], xref, res); |
|
var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); |
|
return ['AlternateCS', numComps, alt, tintFnIR]; |
|
case 'Lab': |
|
default: |
|
error('unimplemented color space object "' + mode + '"'); |
|
} |
|
} else { |
|
error('unrecognized color space object: "' + cs + '"'); |
|
} |
|
return null; |
|
}; |
|
/** |
|
* Checks if a decode map matches the default decode map for a color space. |
|
* This handles the general decode maps where there are two values per |
|
* component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color. |
|
* This does not handle Lab, Indexed, or Pattern decode maps since they are |
|
* slightly different. |
|
* @param {Array} decode Decode map (usually from an image). |
|
* @param {Number} n Number of components the color space has. |
|
*/ |
|
ColorSpace.isDefaultDecode = function colorSpaceIsDefaultDecode(decode, n) { |
|
if (!decode) |
|
return true; |
|
|
|
if (n * 2 !== decode.length) { |
|
warning('The decode map is not the correct length'); |
|
return true; |
|
} |
|
for (var i = 0, ii = decode.length; i < ii; i += 2) { |
|
if (decode[i] != 0 || decode[i + 1] != 1) |
|
return false; |
|
} |
|
return true; |
|
}; |
|
|
|
return ColorSpace; |
|
})(); |
|
|
|
/** |
|
* Alternate color space handles both Separation and DeviceN color spaces. A |
|
* Separation color space is actually just a DeviceN with one color component. |
|
* Both color spaces use a tinting function to convert colors to a base color |
|
* space. |
|
*/ |
|
var AlternateCS = (function AlternateCSClosure() { |
|
function AlternateCS(numComps, base, tintFn) { |
|
this.name = 'Alternate'; |
|
this.numComps = numComps; |
|
this.defaultColor = []; |
|
for (var i = 0; i < numComps; ++i) |
|
this.defaultColor.push(1); |
|
this.base = base; |
|
this.tintFn = tintFn; |
|
} |
|
|
|
AlternateCS.prototype = { |
|
getRgb: function altcs_getRgb(color) { |
|
var tinted = this.tintFn(color); |
|
return this.base.getRgb(tinted); |
|
}, |
|
getRgbBuffer: function altcs_getRgbBuffer(input, bits) { |
|
var tintFn = this.tintFn; |
|
var base = this.base; |
|
var scale = 1 / ((1 << bits) - 1); |
|
var length = input.length; |
|
var pos = 0; |
|
var baseNumComps = base.numComps; |
|
var baseBuf = new Uint8Array(baseNumComps * length); |
|
var numComps = this.numComps; |
|
var scaled = new Array(numComps); |
|
|
|
for (var i = 0; i < length; i += numComps) { |
|
for (var z = 0; z < numComps; ++z) |
|
scaled[z] = input[i + z] * scale; |
|
|
|
var tinted = tintFn(scaled); |
|
for (var j = 0; j < baseNumComps; ++j) |
|
baseBuf[pos++] = 255 * tinted[j]; |
|
} |
|
return base.getRgbBuffer(baseBuf, 8); |
|
}, |
|
isDefaultDecode: function altcs_isDefaultDecode(decodeMap) { |
|
ColorSpace.isDefaultDecode(decodeMap, this.numComps); |
|
} |
|
}; |
|
|
|
return AlternateCS; |
|
})(); |
|
|
|
var PatternCS = (function PatternCSClosure() { |
|
function PatternCS(baseCS) { |
|
this.name = 'Pattern'; |
|
this.base = baseCS; |
|
} |
|
PatternCS.prototype = {}; |
|
|
|
return PatternCS; |
|
})(); |
|
|
|
var IndexedCS = (function IndexedCSClosure() { |
|
function IndexedCS(base, highVal, lookup) { |
|
this.name = 'Indexed'; |
|
this.numComps = 1; |
|
this.defaultColor = [0]; |
|
this.base = base; |
|
this.highVal = highVal; |
|
|
|
var baseNumComps = base.numComps; |
|
var length = baseNumComps * highVal; |
|
var lookupArray = new Uint8Array(length); |
|
|
|
if (isStream(lookup)) { |
|
var bytes = lookup.getBytes(length); |
|
lookupArray.set(bytes); |
|
} else if (isString(lookup)) { |
|
for (var i = 0; i < length; ++i) |
|
lookupArray[i] = lookup.charCodeAt(i); |
|
} else { |
|
error('Unrecognized lookup table: ' + lookup); |
|
} |
|
this.lookup = lookupArray; |
|
} |
|
|
|
IndexedCS.prototype = { |
|
getRgb: function indexcs_getRgb(color) { |
|
var numComps = this.base.numComps; |
|
var start = color[0] * numComps; |
|
var c = []; |
|
|
|
for (var i = start, ii = start + numComps; i < ii; ++i) |
|
c.push(this.lookup[i]); |
|
|
|
return this.base.getRgb(c); |
|
}, |
|
getRgbBuffer: function indexcs_getRgbBuffer(input) { |
|
var base = this.base; |
|
var numComps = base.numComps; |
|
var lookup = this.lookup; |
|
var length = input.length; |
|
var baseBuf = new Uint8Array(length * numComps); |
|
var baseBufPos = 0; |
|
|
|
for (var i = 0; i < length; ++i) { |
|
var lookupPos = input[i] * numComps; |
|
for (var j = 0; j < numComps; ++j) { |
|
baseBuf[baseBufPos++] = lookup[lookupPos + j]; |
|
} |
|
} |
|
|
|
return base.getRgbBuffer(baseBuf, 8); |
|
}, |
|
isDefaultDecode: function indexcs_isDefaultDecode(decodeMap) { |
|
// indexed color maps shouldn't be changed |
|
return true; |
|
} |
|
}; |
|
return IndexedCS; |
|
})(); |
|
|
|
var DeviceGrayCS = (function DeviceGrayCSClosure() { |
|
function DeviceGrayCS() { |
|
this.name = 'DeviceGray'; |
|
this.numComps = 1; |
|
this.defaultColor = [0]; |
|
} |
|
|
|
DeviceGrayCS.prototype = { |
|
getRgb: function graycs_getRgb(color) { |
|
var c = color[0]; |
|
return [c, c, c]; |
|
}, |
|
getRgbBuffer: function graycs_getRgbBuffer(input, bits) { |
|
var scale = 255 / ((1 << bits) - 1); |
|
var length = input.length; |
|
var rgbBuf = new Uint8Array(length * 3); |
|
for (var i = 0, j = 0; i < length; ++i) { |
|
var c = (scale * input[i]) | 0; |
|
rgbBuf[j++] = c; |
|
rgbBuf[j++] = c; |
|
rgbBuf[j++] = c; |
|
} |
|
return rgbBuf; |
|
}, |
|
isDefaultDecode: function graycs_isDefaultDecode(decodeMap) { |
|
ColorSpace.isDefaultDecode(decodeMap, this.numComps); |
|
} |
|
}; |
|
return DeviceGrayCS; |
|
})(); |
|
|
|
var DeviceRgbCS = (function DeviceRgbCSClosure() { |
|
function DeviceRgbCS() { |
|
this.name = 'DeviceRGB'; |
|
this.numComps = 3; |
|
this.defaultColor = [0, 0, 0]; |
|
} |
|
DeviceRgbCS.prototype = { |
|
getRgb: function rgbcs_getRgb(color) { |
|
return color; |
|
}, |
|
getRgbBuffer: function rgbcs_getRgbBuffer(input, bits) { |
|
if (bits == 8) |
|
return input; |
|
var scale = 255 / ((1 << bits) - 1); |
|
var i, length = input.length; |
|
var rgbBuf = new Uint8Array(length); |
|
for (i = 0; i < length; ++i) |
|
rgbBuf[i] = (scale * input[i]) | 0; |
|
return rgbBuf; |
|
}, |
|
isDefaultDecode: function rgbcs_isDefaultDecode(decodeMap) { |
|
ColorSpace.isDefaultDecode(decodeMap, this.numComps); |
|
} |
|
}; |
|
return DeviceRgbCS; |
|
})(); |
|
|
|
var DeviceCmykCS = (function DeviceCmykCSClosure() { |
|
function DeviceCmykCS() { |
|
this.name = 'DeviceCMYK'; |
|
this.numComps = 4; |
|
this.defaultColor = [0, 0, 0, 1]; |
|
} |
|
DeviceCmykCS.prototype = { |
|
getRgb: function cmykcs_getRgb(color) { |
|
var c = color[0], m = color[1], y = color[2], k = color[3]; |
|
var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k; |
|
|
|
var x, r, g, b; |
|
// this is a matrix multiplication, unrolled for performance |
|
// code is taken from the poppler implementation |
|
x = c1 * m1 * y1 * k1; // 0 0 0 0 |
|
r = g = b = x; |
|
x = c1 * m1 * y1 * k; // 0 0 0 1 |
|
r += 0.1373 * x; |
|
g += 0.1216 * x; |
|
b += 0.1255 * x; |
|
x = c1 * m1 * y * k1; // 0 0 1 0 |
|
r += x; |
|
g += 0.9490 * x; |
|
x = c1 * m1 * y * k; // 0 0 1 1 |
|
r += 0.1098 * x; |
|
g += 0.1020 * x; |
|
x = c1 * m * y1 * k1; // 0 1 0 0 |
|
r += 0.9255 * x; |
|
b += 0.5490 * x; |
|
x = c1 * m * y1 * k; // 0 1 0 1 |
|
r += 0.1412 * x; |
|
x = c1 * m * y * k1; // 0 1 1 0 |
|
r += 0.9294 * x; |
|
g += 0.1098 * x; |
|
b += 0.1412 * x; |
|
x = c1 * m * y * k; // 0 1 1 1 |
|
r += 0.1333 * x; |
|
x = c * m1 * y1 * k1; // 1 0 0 0 |
|
g += 0.6784 * x; |
|
b += 0.9373 * x; |
|
x = c * m1 * y1 * k; // 1 0 0 1 |
|
g += 0.0588 * x; |
|
b += 0.1412 * x; |
|
x = c * m1 * y * k1; // 1 0 1 0 |
|
g += 0.6510 * x; |
|
b += 0.3137 * x; |
|
x = c * m1 * y * k; // 1 0 1 1 |
|
g += 0.0745 * x; |
|
x = c * m * y1 * k1; // 1 1 0 0 |
|
r += 0.1804 * x; |
|
g += 0.1922 * x; |
|
b += 0.5725 * x; |
|
x = c * m * y1 * k; // 1 1 0 1 |
|
b += 0.0078 * x; |
|
x = c * m * y * k1; // 1 1 1 0 |
|
r += 0.2118 * x; |
|
g += 0.2119 * x; |
|
b += 0.2235 * x; |
|
|
|
return [r, g, b]; |
|
}, |
|
getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf, bits) { |
|
var scale = 1 / ((1 << bits) - 1); |
|
var length = colorBuf.length / 4; |
|
var rgbBuf = new Uint8Array(length * 3); |
|
var rgbBufPos = 0; |
|
var colorBufPos = 0; |
|
|
|
for (var i = 0; i < length; i++) { |
|
var cmyk = []; |
|
for (var j = 0; j < 4; ++j) |
|
cmyk.push(scale * colorBuf[colorBufPos++]); |
|
|
|
var rgb = this.getRgb(cmyk); |
|
for (var j = 0; j < 3; ++j) |
|
rgbBuf[rgbBufPos++] = Math.round(rgb[j] * 255); |
|
} |
|
|
|
return rgbBuf; |
|
}, |
|
isDefaultDecode: function cmykcs_isDefaultDecode(decodeMap) { |
|
ColorSpace.isDefaultDecode(decodeMap, this.numComps); |
|
} |
|
}; |
|
|
|
return DeviceCmykCS; |
|
})(); |
|
|
|
|