1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-20 15:18:08 +02:00

Merge pull request #19620 from calixteman/cmyk_icc

[api-minor] Use an icc profile for converting CMYK to RGB
This commit is contained in:
calixteman 2025-03-10 16:55:48 +01:00 committed by GitHub
commit 13474aca63
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 224 additions and 274 deletions

View file

@ -25,9 +25,9 @@ import {
LabCS,
PatternCS,
} from "./colorspace.js";
import { CmykICCBasedCS, IccColorSpace } from "./icc_colorspace.js";
import { Dict, Name, Ref } from "./primitives.js";
import { MathClamp, shadow, unreachable, warn } from "../shared/util.js";
import { IccColorSpace } from "./icc_colorspace.js";
import { MissingDataException } from "./core_utils.js";
class ColorSpaceUtils {
@ -205,7 +205,11 @@ class ColorSpaceUtils {
if (IccColorSpace.isUsable) {
try {
const iccCS = new IccColorSpace(stream.getBytes(), numComps);
const iccCS = new IccColorSpace(
stream.getBytes(),
"ICCBased",
numComps
);
if (isRef) {
globalColorSpaceCache.set(/* name = */ null, cs[1], iccCS);
}
@ -285,6 +289,13 @@ class ColorSpaceUtils {
}
static get cmyk() {
if (IccColorSpace.isUsable) {
try {
return shadow(this, "cmyk", new CmykICCBasedCS());
} catch {
warn("CMYK fallback: DeviceCMYK");
}
}
return shadow(this, "cmyk", new DeviceCmykCS());
}
}

View file

@ -94,6 +94,7 @@ const DefaultPartialEvaluatorOptions = Object.freeze({
useWasm: true,
useWorkerFetch: true,
cMapUrl: null,
iccUrl: null,
standardFontDataUrl: null,
wasmUrl: null,
});

View file

@ -41,12 +41,12 @@ class IccColorSpace extends ColorSpace {
qcms_drop_transformer(transformer);
});
constructor(iccProfile, numComps) {
constructor(iccProfile, name, numComps) {
if (!IccColorSpace.isUsable) {
throw new Error("No ICC color space support");
}
super("ICCBased", numComps);
super(name, numComps);
let inType;
switch (numComps) {
@ -104,11 +104,13 @@ class IccColorSpace extends ColorSpace {
src[i] *= scale;
}
}
QCMS._mustAddAlpha = alpha01 && dest.buffer === src.buffer;
QCMS._destBuffer = dest.subarray(
destOffset,
destOffset + count * (3 + alpha01)
);
qcms_convert_array(this.#transformer, src);
QCMS._mustAddAlpha = false;
QCMS._destBuffer = null;
}
@ -152,4 +154,21 @@ class IccColorSpace extends ColorSpace {
}
}
export { IccColorSpace };
class CmykICCBasedCS extends IccColorSpace {
static #iccUrl;
constructor() {
const filename = "CGATS001Compat-v2-micro.icc";
const xhr = new XMLHttpRequest();
xhr.open("GET", `${CmykICCBasedCS.#iccUrl}${filename}`, false);
xhr.responseType = "arraybuffer";
xhr.send(null);
super(new Uint8Array(xhr.response), "DeviceCMYK", 4);
}
static setOptions({ iccUrl }) {
this.#iccUrl = iccUrl;
}
}
export { CmykICCBasedCS, IccColorSpace };

View file

@ -14,6 +14,7 @@
*/
import { assert, BaseException, warn } from "../shared/util.js";
import { ColorSpaceUtils } from "./colorspace_utils.js";
import { grayToRGBA } from "../shared/image_utils.js";
import { readUint16 } from "./core_utils.js";
@ -1317,141 +1318,13 @@ class JpegImage {
}
_convertYcckToRgb(data) {
let Y, Cb, Cr, k;
let offset = 0;
for (let i = 0, length = data.length; i < length; i += 4) {
Y = data[i];
Cb = data[i + 1];
Cr = data[i + 2];
k = data[i + 3];
data[offset++] =
-122.67195406894 +
Cb *
(-6.60635669420364e-5 * Cb +
0.000437130475926232 * Cr -
5.4080610064599e-5 * Y +
0.00048449797120281 * k -
0.154362151871126) +
Cr *
(-0.000957964378445773 * Cr +
0.000817076911346625 * Y -
0.00477271405408747 * k +
1.53380253221734) +
Y *
(0.000961250184130688 * Y -
0.00266257332283933 * k +
0.48357088451265) +
k * (-0.000336197177618394 * k + 0.484791561490776);
data[offset++] =
107.268039397724 +
Cb *
(2.19927104525741e-5 * Cb -
0.000640992018297945 * Cr +
0.000659397001245577 * Y +
0.000426105652938837 * k -
0.176491792462875) +
Cr *
(-0.000778269941513683 * Cr +
0.00130872261408275 * Y +
0.000770482631801132 * k -
0.151051492775562) +
Y *
(0.00126935368114843 * Y -
0.00265090189010898 * k +
0.25802910206845) +
k * (-0.000318913117588328 * k - 0.213742400323665);
data[offset++] =
-20.810012546947 +
Cb *
(-0.000570115196973677 * Cb -
2.63409051004589e-5 * Cr +
0.0020741088115012 * Y -
0.00288260236853442 * k +
0.814272968359295) +
Cr *
(-1.53496057440975e-5 * Cr -
0.000132689043961446 * Y +
0.000560833691242812 * k -
0.195152027534049) +
Y *
(0.00174418132927582 * Y -
0.00255243321439347 * k +
0.116935020465145) +
k * (-0.000343531996510555 * k + 0.24165260232407);
}
// Ensure that only the converted RGB data is returned.
return data.subarray(0, offset);
this._convertYcckToCmyk(data);
return this._convertCmykToRgb(data);
}
_convertYcckToRgba(data) {
for (let i = 0, length = data.length; i < length; i += 4) {
const Y = data[i];
const Cb = data[i + 1];
const Cr = data[i + 2];
const k = data[i + 3];
data[i] =
-122.67195406894 +
Cb *
(-6.60635669420364e-5 * Cb +
0.000437130475926232 * Cr -
5.4080610064599e-5 * Y +
0.00048449797120281 * k -
0.154362151871126) +
Cr *
(-0.000957964378445773 * Cr +
0.000817076911346625 * Y -
0.00477271405408747 * k +
1.53380253221734) +
Y *
(0.000961250184130688 * Y -
0.00266257332283933 * k +
0.48357088451265) +
k * (-0.000336197177618394 * k + 0.484791561490776);
data[i + 1] =
107.268039397724 +
Cb *
(2.19927104525741e-5 * Cb -
0.000640992018297945 * Cr +
0.000659397001245577 * Y +
0.000426105652938837 * k -
0.176491792462875) +
Cr *
(-0.000778269941513683 * Cr +
0.00130872261408275 * Y +
0.000770482631801132 * k -
0.151051492775562) +
Y *
(0.00126935368114843 * Y -
0.00265090189010898 * k +
0.25802910206845) +
k * (-0.000318913117588328 * k - 0.213742400323665);
data[i + 2] =
-20.810012546947 +
Cb *
(-0.000570115196973677 * Cb -
2.63409051004589e-5 * Cr +
0.0020741088115012 * Y -
0.00288260236853442 * k +
0.814272968359295) +
Cr *
(-1.53496057440975e-5 * Cr -
0.000132689043961446 * Y +
0.000560833691242812 * k -
0.195152027534049) +
Y *
(0.00174418132927582 * Y -
0.00255243321439347 * k +
0.116935020465145) +
k * (-0.000343531996510555 * k + 0.24165260232407);
data[i + 3] = 255;
}
return data;
this._convertYcckToCmyk(data);
return this._convertCmykToRgba(data);
}
_convertYcckToCmyk(data) {
@ -1469,140 +1342,13 @@ class JpegImage {
}
_convertCmykToRgb(data) {
let c, m, y, k;
let offset = 0;
for (let i = 0, length = data.length; i < length; i += 4) {
c = data[i];
m = data[i + 1];
y = data[i + 2];
k = data[i + 3];
data[offset++] =
255 +
c *
(-0.00006747147073602441 * c +
0.0008379262121013727 * m +
0.0002894718188643294 * y +
0.003264231057537806 * k -
1.1185611867203937) +
m *
(0.000026374107616089405 * m -
0.00008626949158638572 * y -
0.0002748769067499491 * k -
0.02155688794978967) +
y *
(-0.00003878099212869363 * y -
0.0003267808279485286 * k +
0.0686742238595345) -
k * (0.0003361971776183937 * k + 0.7430659151342254);
data[offset++] =
255 +
c *
(0.00013596372813588848 * c +
0.000924537132573585 * m +
0.00010567359618683593 * y +
0.0004791864687436512 * k -
0.3109689587515875) +
m *
(-0.00023545346108370344 * m +
0.0002702845253534714 * y +
0.0020200308977307156 * k -
0.7488052167015494) +
y *
(0.00006834815998235662 * y +
0.00015168452363460973 * k -
0.09751927774728933) -
k * (0.0003189131175883281 * k + 0.7364883807733168);
data[offset++] =
255 +
c *
(0.000013598650411385307 * c +
0.00012423956175490851 * m +
0.0004751985097583589 * y -
0.0000036729317476630422 * k -
0.05562186980264034) +
m *
(0.00016141380598724676 * m +
0.0009692239130725186 * y +
0.0007782692450036253 * k -
0.44015232367526463) +
y *
(5.068882914068769e-7 * y +
0.0017778369011375071 * k -
0.7591454649749609) -
k * (0.0003435319965105553 * k + 0.7063770186160144);
}
// Ensure that only the converted RGB data is returned.
return data.subarray(0, offset);
const count = data.length / 4;
ColorSpaceUtils.cmyk.getRgbBuffer(data, 0, count, data, 0, 8, 0);
return data.subarray(0, count * 3);
}
_convertCmykToRgba(data) {
for (let i = 0, length = data.length; i < length; i += 4) {
const c = data[i];
const m = data[i + 1];
const y = data[i + 2];
const k = data[i + 3];
data[i] =
255 +
c *
(-0.00006747147073602441 * c +
0.0008379262121013727 * m +
0.0002894718188643294 * y +
0.003264231057537806 * k -
1.1185611867203937) +
m *
(0.000026374107616089405 * m -
0.00008626949158638572 * y -
0.0002748769067499491 * k -
0.02155688794978967) +
y *
(-0.00003878099212869363 * y -
0.0003267808279485286 * k +
0.0686742238595345) -
k * (0.0003361971776183937 * k + 0.7430659151342254);
data[i + 1] =
255 +
c *
(0.00013596372813588848 * c +
0.000924537132573585 * m +
0.00010567359618683593 * y +
0.0004791864687436512 * k -
0.3109689587515875) +
m *
(-0.00023545346108370344 * m +
0.0002702845253534714 * y +
0.0020200308977307156 * k -
0.7488052167015494) +
y *
(0.00006834815998235662 * y +
0.00015168452363460973 * k -
0.09751927774728933) -
k * (0.0003189131175883281 * k + 0.7364883807733168);
data[i + 2] =
255 +
c *
(0.000013598650411385307 * c +
0.00012423956175490851 * m +
0.0004751985097583589 * y -
0.0000036729317476630422 * k -
0.05562186980264034) +
m *
(0.00016141380598724676 * m +
0.0009692239130725186 * y +
0.0007782692450036253 * k -
0.44015232367526463) +
y *
(5.068882914068769e-7 * y +
0.0017778369011375071 * k -
0.7591454649749609) -
k * (0.0003435319965105553 * k + 0.7063770186160144);
data[i + 3] = 255;
}
ColorSpaceUtils.cmyk.getRgbBuffer(data, 0, data.length / 4, data, 0, 8, 1);
return data;
}

View file

@ -13,6 +13,7 @@
* limitations under the License.
*/
import { CmykICCBasedCS, IccColorSpace } from "./icc_colorspace.js";
import {
createValidAbsoluteUrl,
FeatureTest,
@ -20,7 +21,6 @@ import {
warn,
} from "../shared/util.js";
import { ChunkedStreamManager } from "./chunked_stream.js";
import { IccColorSpace } from "./icc_colorspace.js";
import { ImageResizer } from "./image_resizer.js";
import { JpegStream } from "./jpeg_stream.js";
import { JpxImage } from "./jpx.js";
@ -78,6 +78,7 @@ class BasePdfManager {
const options = { ...evaluatorOptions, handler };
JpxImage.setOptions(options);
IccColorSpace.setOptions(options);
CmykICCBasedCS.setOptions(options);
}
get docId() {

View file

@ -125,6 +125,8 @@ const RENDERING_CANCELLED_TIMEOUT = 100; // ms
* @property {Object} [CMapReaderFactory] - The factory that will be used when
* reading built-in CMap files.
* The default value is {DOMCMapReaderFactory}.
* @property {string} [iccUrl] - The URL where the predefined ICC profiles are
* located. Include the trailing slash.
* @property {boolean} [useSystemFonts] - When `true`, fonts that aren't
* embedded in the PDF document will fallback to a system font.
* The default value is `true` in web environments and `false` in Node.js;
@ -269,6 +271,7 @@ function getDocument(src = {}) {
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
? NodeCMapReaderFactory
: DOMCMapReaderFactory);
const iccUrl = getFactoryUrlProp(src.iccUrl);
const standardFontDataUrl = getFactoryUrlProp(src.standardFontDataUrl);
const StandardFontDataFactory =
src.StandardFontDataFactory ||
@ -418,6 +421,7 @@ function getDocument(src = {}) {
useWasm,
useWorkerFetch,
cMapUrl,
iccUrl,
standardFontDataUrl,
wasmUrl,
},