1//========================================================================
2//
3// GfxState.cc
4//
5// Copyright 1996-2003 Glyph & Cog, LLC
6//
7//========================================================================
8
9//========================================================================
10//
11// Modified under the Poppler project - http://poppler.freedesktop.org
12//
13// All changes made under the Poppler project to this file are licensed
14// under GPL version 2 or later
15//
16// Copyright (C) 2005 Kristian Høgsberg <krh@redhat.com>
17// Copyright (C) 2006, 2007 Jeff Muizelaar <jeff@infidigm.net>
18// Copyright (C) 2006, 2010 Carlos Garcia Campos <carlosgc@gnome.org>
19// Copyright (C) 2006-2022 Albert Astals Cid <aacid@kde.org>
20// Copyright (C) 2009, 2012 Koji Otani <sho@bbr.jp>
21// Copyright (C) 2009, 2011-2016, 2020, 2023 Thomas Freitag <Thomas.Freitag@alfa.de>
22// Copyright (C) 2009, 2019 Christian Persch <chpe@gnome.org>
23// Copyright (C) 2010 Paweł Wiejacha <pawel.wiejacha@gmail.com>
24// Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
25// Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com>
26// Copyright (C) 2012, 2020 William Bader <williambader@hotmail.com>
27// Copyright (C) 2013 Lu Wang <coolwanglu@gmail.com>
28// Copyright (C) 2013 Hib Eris <hib@hiberis.nl>
29// Copyright (C) 2013 Fabio D'Urso <fabiodurso@hotmail.it>
30// Copyright (C) 2015, 2020 Adrian Johnson <ajohnson@redneon.com>
31// Copyright (C) 2016 Marek Kasik <mkasik@redhat.com>
32// Copyright (C) 2017, 2019, 2022 Oliver Sander <oliver.sander@tu-dresden.de>
33// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
34// Copyright (C) 2018 Volker Krause <vkrause@kde.org>
35// Copyright (C) 2018, 2019 Adam Reichold <adam.reichold@t-online.de>
36// Copyright (C) 2019 LE GARREC Vincent <legarrec.vincent@gmail.com>
37// Copyright (C) 2020, 2021 Philipp Knechtges <philipp-dev@knechtges.com>
38// Copyright (C) 2020 Lluís Batlle i Rossell <viric@viric.name>
39//
40// To see a description of the changes please see the Changelog file that
41// came with your tarball or type make ChangeLog if you are building from git
42//
43//========================================================================
44
45#include <config.h>
46
47#include <algorithm>
48#include <memory>
49#include <cstddef>
50#include <cmath>
51#include <cstring>
52#include "goo/gfile.h"
53#include "goo/gmem.h"
54#include "Error.h"
55#include "Object.h"
56#include "Array.h"
57#include "Page.h"
58#include "Gfx.h"
59#include "GfxState.h"
60#include "GfxState_helpers.h"
61#include "GfxFont.h"
62#include "GlobalParams.h"
63#include "PopplerCache.h"
64#include "OutputDev.h"
65#include "splash/SplashTypes.h"
66
67//------------------------------------------------------------------------
68
69// Max depth of nested color spaces. This is used to catch infinite
70// loops in the color space object structure.
71#define colorSpaceRecursionLimit 8
72
73//------------------------------------------------------------------------
74
75bool Matrix::invertTo(Matrix *other) const
76{
77 const double det_denominator = determinant();
78 if (unlikely(det_denominator == 0)) {
79 *other = { 1, 0, 0, 1, 0, 0 };
80 return false;
81 }
82
83 const double det = 1 / det_denominator;
84 other->m[0] = m[3] * det;
85 other->m[1] = -m[1] * det;
86 other->m[2] = -m[2] * det;
87 other->m[3] = m[0] * det;
88 other->m[4] = (m[2] * m[5] - m[3] * m[4]) * det;
89 other->m[5] = (m[1] * m[4] - m[0] * m[5]) * det;
90
91 return true;
92}
93
94void Matrix::translate(double tx, double ty)
95{
96 double x0 = tx * m[0] + ty * m[2] + m[4];
97 double y0 = tx * m[1] + ty * m[3] + m[5];
98 m[4] = x0;
99 m[5] = y0;
100}
101
102void Matrix::scale(double sx, double sy)
103{
104 m[0] *= sx;
105 m[1] *= sx;
106 m[2] *= sy;
107 m[3] *= sy;
108}
109
110void Matrix::transform(double x, double y, double *tx, double *ty) const
111{
112 double temp_x, temp_y;
113
114 temp_x = m[0] * x + m[2] * y + m[4];
115 temp_y = m[1] * x + m[3] * y + m[5];
116
117 *tx = temp_x;
118 *ty = temp_y;
119}
120
121// Matrix norm, taken from _cairo_matrix_transformed_circle_major_axis
122double Matrix::norm() const
123{
124 double f, g, h, i, j;
125
126 i = m[0] * m[0] + m[1] * m[1];
127 j = m[2] * m[2] + m[3] * m[3];
128
129 f = 0.5 * (i + j);
130 g = 0.5 * (i - j);
131 h = m[0] * m[2] + m[1] * m[3];
132
133 return sqrt(x: f + hypot(x: g, y: h));
134}
135
136//------------------------------------------------------------------------
137
138struct GfxBlendModeInfo
139{
140 const char *name;
141 GfxBlendMode mode;
142};
143
144static const GfxBlendModeInfo gfxBlendModeNames[] = { { .name: "Normal", .mode: gfxBlendNormal }, { .name: "Compatible", .mode: gfxBlendNormal },
145 { .name: "Multiply", .mode: gfxBlendMultiply }, { .name: "Screen", .mode: gfxBlendScreen },
146 { .name: "Overlay", .mode: gfxBlendOverlay }, { .name: "Darken", .mode: gfxBlendDarken },
147 { .name: "Lighten", .mode: gfxBlendLighten }, { .name: "ColorDodge", .mode: gfxBlendColorDodge },
148 { .name: "ColorBurn", .mode: gfxBlendColorBurn }, { .name: "HardLight", .mode: gfxBlendHardLight },
149 { .name: "SoftLight", .mode: gfxBlendSoftLight }, { .name: "Difference", .mode: gfxBlendDifference },
150 { .name: "Exclusion", .mode: gfxBlendExclusion }, { .name: "Hue", .mode: gfxBlendHue },
151 { .name: "Saturation", .mode: gfxBlendSaturation }, { .name: "Color", .mode: gfxBlendColor },
152 { .name: "Luminosity", .mode: gfxBlendLuminosity } };
153
154#define nGfxBlendModeNames ((int)((sizeof(gfxBlendModeNames) / sizeof(GfxBlendModeInfo))))
155
156//------------------------------------------------------------------------
157//
158// NB: This must match the GfxColorSpaceMode enum defined in
159// GfxState.h
160static const char *gfxColorSpaceModeNames[] = { "DeviceGray", "CalGray", "DeviceRGB", "CalRGB", "DeviceCMYK", "Lab", "ICCBased", "Indexed", "Separation", "DeviceN", "Pattern" };
161
162#define nGfxColorSpaceModes ((sizeof(gfxColorSpaceModeNames) / sizeof(char *)))
163
164#ifdef USE_CMS
165
166static const std::map<unsigned int, unsigned int>::size_type CMSCACHE_LIMIT = 2048;
167
168# include <lcms2.h>
169# define LCMS_FLAGS cmsFLAGS_NOOPTIMIZE | cmsFLAGS_BLACKPOINTCOMPENSATION
170
171static void lcmsprofiledeleter(void *profile)
172{
173 cmsCloseProfile(profile);
174}
175
176GfxLCMSProfilePtr make_GfxLCMSProfilePtr(void *profile)
177{
178 if (profile == nullptr) {
179 return GfxLCMSProfilePtr();
180 }
181 return GfxLCMSProfilePtr(profile, lcmsprofiledeleter);
182}
183
184void GfxColorTransform::doTransform(void *in, void *out, unsigned int size)
185{
186 cmsDoTransform(transform, in, out, size);
187}
188
189// transformA should be a cmsHTRANSFORM
190GfxColorTransform::GfxColorTransform(void *transformA, int cmsIntentA, unsigned int inputPixelTypeA, unsigned int transformPixelTypeA)
191{
192 transform = transformA;
193 cmsIntent = cmsIntentA;
194 inputPixelType = inputPixelTypeA;
195 transformPixelType = transformPixelTypeA;
196}
197
198GfxColorTransform::~GfxColorTransform()
199{
200 cmsDeleteTransform(transform);
201}
202
203// convert color space signature to cmsColor type
204static unsigned int getCMSColorSpaceType(cmsColorSpaceSignature cs);
205static unsigned int getCMSNChannels(cmsColorSpaceSignature cs);
206
207#endif
208
209//------------------------------------------------------------------------
210// GfxColorSpace
211//------------------------------------------------------------------------
212
213GfxColorSpace::GfxColorSpace()
214{
215 overprintMask = 0x0f;
216 mapping = nullptr;
217}
218
219GfxColorSpace::~GfxColorSpace() { }
220
221GfxColorSpace *GfxColorSpace::parse(GfxResources *res, Object *csObj, OutputDev *out, GfxState *state, int recursion)
222{
223 GfxColorSpace *cs;
224 Object obj1;
225
226 if (recursion > colorSpaceRecursionLimit) {
227 error(category: errSyntaxError, pos: -1, msg: "Loop detected in color space objects");
228 return nullptr;
229 }
230
231 cs = nullptr;
232 if (csObj->isName()) {
233 if (csObj->isName(nameA: "DeviceGray") || csObj->isName(nameA: "G")) {
234 if (res != nullptr) {
235 Object objCS = res->lookupColorSpace(name: "DefaultGray");
236 if (objCS.isNull()) {
237 cs = state->copyDefaultGrayColorSpace();
238 } else {
239 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
240 }
241 } else {
242 cs = state->copyDefaultGrayColorSpace();
243 }
244 } else if (csObj->isName(nameA: "DeviceRGB") || csObj->isName(nameA: "RGB")) {
245 if (res != nullptr) {
246 Object objCS = res->lookupColorSpace(name: "DefaultRGB");
247 if (objCS.isNull()) {
248 cs = state->copyDefaultRGBColorSpace();
249 } else {
250 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
251 }
252 } else {
253 cs = state->copyDefaultRGBColorSpace();
254 }
255 } else if (csObj->isName(nameA: "DeviceCMYK") || csObj->isName(nameA: "CMYK")) {
256 if (res != nullptr) {
257 Object objCS = res->lookupColorSpace(name: "DefaultCMYK");
258 if (objCS.isNull()) {
259 cs = state->copyDefaultCMYKColorSpace();
260 } else {
261 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
262 }
263 } else {
264 cs = state->copyDefaultCMYKColorSpace();
265 }
266 } else if (csObj->isName(nameA: "Pattern")) {
267 cs = new GfxPatternColorSpace(nullptr);
268 } else {
269 error(category: errSyntaxWarning, pos: -1, msg: "Bad color space '{0:s}'", csObj->getName());
270 }
271 } else if (csObj->isArray() && csObj->arrayGetLength() > 0) {
272 obj1 = csObj->arrayGet(i: 0);
273 if (obj1.isName(nameA: "DeviceGray") || obj1.isName(nameA: "G")) {
274 if (res != nullptr) {
275 Object objCS = res->lookupColorSpace(name: "DefaultGray");
276 if (objCS.isNull()) {
277 cs = state->copyDefaultGrayColorSpace();
278 } else {
279 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
280 }
281 } else {
282 cs = state->copyDefaultGrayColorSpace();
283 }
284 } else if (obj1.isName(nameA: "DeviceRGB") || obj1.isName(nameA: "RGB")) {
285 if (res != nullptr) {
286 Object objCS = res->lookupColorSpace(name: "DefaultRGB");
287 if (objCS.isNull()) {
288 cs = state->copyDefaultRGBColorSpace();
289 } else {
290 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
291 }
292 } else {
293 cs = state->copyDefaultRGBColorSpace();
294 }
295 } else if (obj1.isName(nameA: "DeviceCMYK") || obj1.isName(nameA: "CMYK")) {
296 if (res != nullptr) {
297 Object objCS = res->lookupColorSpace(name: "DefaultCMYK");
298 if (objCS.isNull()) {
299 cs = state->copyDefaultCMYKColorSpace();
300 } else {
301 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
302 }
303 } else {
304 cs = state->copyDefaultCMYKColorSpace();
305 }
306 } else if (obj1.isName(nameA: "CalGray")) {
307 cs = GfxCalGrayColorSpace::parse(arr: csObj->getArray(), state);
308 } else if (obj1.isName(nameA: "CalRGB")) {
309 cs = GfxCalRGBColorSpace::parse(arr: csObj->getArray(), state);
310 } else if (obj1.isName(nameA: "Lab")) {
311 cs = GfxLabColorSpace::parse(arr: csObj->getArray(), state);
312 } else if (obj1.isName(nameA: "ICCBased")) {
313 cs = GfxICCBasedColorSpace::parse(arr: csObj->getArray(), out, state, recursion);
314 } else if (obj1.isName(nameA: "Indexed") || obj1.isName(nameA: "I")) {
315 cs = GfxIndexedColorSpace::parse(res, arr: csObj->getArray(), out, state, recursion);
316 } else if (obj1.isName(nameA: "Separation")) {
317 cs = GfxSeparationColorSpace::parse(res, arr: csObj->getArray(), out, state, recursion);
318 } else if (obj1.isName(nameA: "DeviceN")) {
319 cs = GfxDeviceNColorSpace::parse(res, arr: csObj->getArray(), out, state, recursion);
320 } else if (obj1.isName(nameA: "Pattern")) {
321 cs = GfxPatternColorSpace::parse(res, arr: csObj->getArray(), out, state, recursion);
322 } else {
323 error(category: errSyntaxWarning, pos: -1, msg: "Bad color space");
324 }
325 } else if (csObj->isDict()) {
326 obj1 = csObj->dictLookup(key: "ColorSpace");
327 if (obj1.isName(nameA: "DeviceGray")) {
328 if (res != nullptr) {
329 Object objCS = res->lookupColorSpace(name: "DefaultGray");
330 if (objCS.isNull()) {
331 cs = state->copyDefaultGrayColorSpace();
332 } else {
333 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
334 }
335 } else {
336 cs = state->copyDefaultGrayColorSpace();
337 }
338 } else if (obj1.isName(nameA: "DeviceRGB")) {
339 if (res != nullptr) {
340 Object objCS = res->lookupColorSpace(name: "DefaultRGB");
341 if (objCS.isNull()) {
342 cs = state->copyDefaultRGBColorSpace();
343 } else {
344 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
345 }
346 } else {
347 cs = state->copyDefaultRGBColorSpace();
348 }
349 } else if (obj1.isName(nameA: "DeviceCMYK")) {
350 if (res != nullptr) {
351 Object objCS = res->lookupColorSpace(name: "DefaultCMYK");
352 if (objCS.isNull()) {
353 cs = state->copyDefaultCMYKColorSpace();
354 } else {
355 cs = GfxColorSpace::parse(res: nullptr, csObj: &objCS, out, state);
356 }
357 } else {
358 cs = state->copyDefaultCMYKColorSpace();
359 }
360 } else {
361 error(category: errSyntaxWarning, pos: -1, msg: "Bad color space dict'");
362 }
363 } else {
364 error(category: errSyntaxWarning, pos: -1, msg: "Bad color space - expected name or array or dict");
365 }
366 return cs;
367}
368
369void GfxColorSpace::createMapping(std::vector<GfxSeparationColorSpace *> *separationList, int maxSepComps)
370{
371 return;
372}
373
374void GfxColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
375{
376 int i;
377
378 for (i = 0; i < getNComps(); ++i) {
379 decodeLow[i] = 0;
380 decodeRange[i] = 1;
381 }
382}
383
384int GfxColorSpace::getNumColorSpaceModes()
385{
386 return nGfxColorSpaceModes;
387}
388
389const char *GfxColorSpace::getColorSpaceModeName(int idx)
390{
391 return gfxColorSpaceModeNames[idx];
392}
393
394#ifdef USE_CMS
395
396static void CMSError(cmsContext /*contextId*/, cmsUInt32Number /*ecode*/, const char *text)
397{
398 error(errSyntaxWarning, -1, "{0:s}", text);
399}
400
401unsigned int getCMSColorSpaceType(cmsColorSpaceSignature cs)
402{
403 switch (cs) {
404 case cmsSigXYZData:
405 return PT_XYZ;
406 break;
407 case cmsSigLabData:
408 return PT_Lab;
409 break;
410 case cmsSigLuvData:
411 return PT_YUV;
412 break;
413 case cmsSigYCbCrData:
414 return PT_YCbCr;
415 break;
416 case cmsSigYxyData:
417 return PT_Yxy;
418 break;
419 case cmsSigRgbData:
420 return PT_RGB;
421 break;
422 case cmsSigGrayData:
423 return PT_GRAY;
424 break;
425 case cmsSigHsvData:
426 return PT_HSV;
427 break;
428 case cmsSigHlsData:
429 return PT_HLS;
430 break;
431 case cmsSigCmykData:
432 return PT_CMYK;
433 break;
434 case cmsSigCmyData:
435 return PT_CMY;
436 break;
437 case cmsSig2colorData:
438 case cmsSig3colorData:
439 case cmsSig4colorData:
440 case cmsSig5colorData:
441 case cmsSig6colorData:
442 case cmsSig7colorData:
443 case cmsSig8colorData:
444 case cmsSig9colorData:
445 case cmsSig10colorData:
446 case cmsSig11colorData:
447 case cmsSig12colorData:
448 case cmsSig13colorData:
449 case cmsSig14colorData:
450 case cmsSig15colorData:
451 default:
452 break;
453 }
454 return PT_RGB;
455}
456
457unsigned int getCMSNChannels(cmsColorSpaceSignature cs)
458{
459 switch (cs) {
460 case cmsSigXYZData:
461 case cmsSigLuvData:
462 case cmsSigLabData:
463 case cmsSigYCbCrData:
464 case cmsSigYxyData:
465 case cmsSigRgbData:
466 case cmsSigHsvData:
467 case cmsSigHlsData:
468 case cmsSigCmyData:
469 case cmsSig3colorData:
470 return 3;
471 break;
472 case cmsSigGrayData:
473 return 1;
474 break;
475 case cmsSigCmykData:
476 case cmsSig4colorData:
477 return 4;
478 break;
479 case cmsSig2colorData:
480 return 2;
481 break;
482 case cmsSig5colorData:
483 return 5;
484 break;
485 case cmsSig6colorData:
486 return 6;
487 break;
488 case cmsSig7colorData:
489 return 7;
490 break;
491 case cmsSig8colorData:
492 return 8;
493 break;
494 case cmsSig9colorData:
495 return 9;
496 break;
497 case cmsSig10colorData:
498 return 10;
499 break;
500 case cmsSig11colorData:
501 return 11;
502 break;
503 case cmsSig12colorData:
504 return 12;
505 break;
506 case cmsSig13colorData:
507 return 13;
508 break;
509 case cmsSig14colorData:
510 return 14;
511 break;
512 case cmsSig15colorData:
513 return 15;
514 default:
515 break;
516 }
517 return 3;
518}
519#endif
520
521//------------------------------------------------------------------------
522// GfxDeviceGrayColorSpace
523//------------------------------------------------------------------------
524
525GfxDeviceGrayColorSpace::GfxDeviceGrayColorSpace() { }
526
527GfxDeviceGrayColorSpace::~GfxDeviceGrayColorSpace() { }
528
529GfxColorSpace *GfxDeviceGrayColorSpace::copy() const
530{
531 return new GfxDeviceGrayColorSpace();
532}
533
534void GfxDeviceGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
535{
536 *gray = clip01(x: color->c[0]);
537}
538
539void GfxDeviceGrayColorSpace::getGrayLine(unsigned char *in, unsigned char *out, int length)
540{
541 memcpy(dest: out, src: in, n: length);
542}
543
544void GfxDeviceGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
545{
546 rgb->r = rgb->g = rgb->b = clip01(x: color->c[0]);
547}
548
549void GfxDeviceGrayColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
550{
551 int i;
552
553 for (i = 0; i < length; i++) {
554 out[i] = (in[i] << 16) | (in[i] << 8) | (in[i] << 0);
555 }
556}
557
558void GfxDeviceGrayColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
559{
560 for (int i = 0; i < length; i++) {
561 *out++ = in[i];
562 *out++ = in[i];
563 *out++ = in[i];
564 }
565}
566
567void GfxDeviceGrayColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
568{
569 for (int i = 0; i < length; i++) {
570 *out++ = in[i];
571 *out++ = in[i];
572 *out++ = in[i];
573 *out++ = 255;
574 }
575}
576
577void GfxDeviceGrayColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
578{
579 for (int i = 0; i < length; i++) {
580 *out++ = 0;
581 *out++ = 0;
582 *out++ = 0;
583 *out++ = in[i];
584 }
585}
586
587void GfxDeviceGrayColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
588{
589 for (int i = 0; i < length; i++) {
590 for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
591 out[j] = 0;
592 }
593 out[4] = in[i];
594 out += (SPOT_NCOMPS + 4);
595 }
596}
597
598void GfxDeviceGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
599{
600 cmyk->c = cmyk->m = cmyk->y = 0;
601 cmyk->k = clip01(gfxColorComp1 - color->c[0]);
602}
603
604void GfxDeviceGrayColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
605{
606 clearGfxColor(gfxColor: deviceN);
607 deviceN->c[3] = clip01(gfxColorComp1 - color->c[0]);
608}
609
610void GfxDeviceGrayColorSpace::getDefaultColor(GfxColor *color) const
611{
612 color->c[0] = 0;
613}
614
615//------------------------------------------------------------------------
616// GfxCalGrayColorSpace
617//------------------------------------------------------------------------
618
619GfxCalGrayColorSpace::GfxCalGrayColorSpace()
620{
621 whiteX = whiteY = whiteZ = 1;
622 blackX = blackY = blackZ = 0;
623 gamma = 1;
624}
625
626GfxCalGrayColorSpace::~GfxCalGrayColorSpace() { }
627
628GfxColorSpace *GfxCalGrayColorSpace::copy() const
629{
630 GfxCalGrayColorSpace *cs;
631
632 cs = new GfxCalGrayColorSpace();
633 cs->whiteX = whiteX;
634 cs->whiteY = whiteY;
635 cs->whiteZ = whiteZ;
636 cs->blackX = blackX;
637 cs->blackY = blackY;
638 cs->blackZ = blackZ;
639 cs->gamma = gamma;
640#ifdef USE_CMS
641 cs->transform = transform;
642#endif
643 return cs;
644}
645
646// This is the inverse of MatrixLMN in Example 4.10 from the PostScript
647// Language Reference, Third Edition.
648static const double xyzrgb[3][3] = { { 3.240449, -1.537136, -0.498531 }, { -0.969265, 1.876011, 0.041556 }, { 0.055643, -0.204026, 1.057229 } };
649
650// From the same reference as above, the inverse of the DecodeLMN function.
651// This is essentially the gamma function of the sRGB profile.
652static double srgb_gamma_function(double x)
653{
654 // 0.04045 is what lcms2 uses, but the PS Reference Example 4.10 specifies 0.03928???
655 // if (x <= 0.04045 / 12.92321) {
656 if (x <= 0.03928 / 12.92321) {
657 return x * 12.92321;
658 }
659 return 1.055 * pow(x: x, y: 1.0 / 2.4) - 0.055;
660}
661
662// D65 is the white point of the sRGB profile as it is specified above in the xyzrgb array
663static const double white_d65_X = 0.9505;
664static const double white_d65_Y = 1.0;
665static const double white_d65_Z = 1.0890;
666
667#ifdef USE_CMS
668// D50 is the default white point as used in ICC profiles and in the lcms2 library
669static const double white_d50_X = 0.96422;
670static const double white_d50_Y = 1.0;
671static const double white_d50_Z = 0.82521;
672
673static void inline bradford_transform_to_d50(double &X, double &Y, double &Z, const double source_whiteX, const double source_whiteY, const double source_whiteZ)
674{
675 if (source_whiteX == white_d50_X && source_whiteY == white_d50_Y && source_whiteZ == white_d50_Z) {
676 // early exit if noop
677 return;
678 }
679 // at first apply Bradford matrix
680 double rho_in = 0.8951000 * X + 0.2664000 * Y - 0.1614000 * Z;
681 double gamma_in = -0.7502000 * X + 1.7135000 * Y + 0.0367000 * Z;
682 double beta_in = 0.0389000 * X - 0.0685000 * Y + 1.0296000 * Z;
683
684 // apply a diagonal matrix with the diagonal entries being the inverse bradford-transformed white point
685 rho_in /= 0.8951000 * source_whiteX + 0.2664000 * source_whiteY - 0.1614000 * source_whiteZ;
686 gamma_in /= -0.7502000 * source_whiteX + 1.7135000 * source_whiteY + 0.0367000 * source_whiteZ;
687 beta_in /= 0.0389000 * source_whiteX - 0.0685000 * source_whiteY + 1.0296000 * source_whiteZ;
688
689 // now revert the two steps above, but substituting the source white point by the device white point (D50)
690 // Since the white point is known a priori this has been combined into a single operation.
691 X = 0.98332566 * rho_in - 0.15005819 * gamma_in + 0.13095252 * beta_in;
692 Y = 0.43069901 * rho_in + 0.52894900 * gamma_in + 0.04035199 * beta_in;
693 Z = 0.00849698 * rho_in + 0.04086079 * gamma_in + 0.79284618 * beta_in;
694}
695#endif
696
697static void inline bradford_transform_to_d65(double &X, double &Y, double &Z, const double source_whiteX, const double source_whiteY, const double source_whiteZ)
698{
699 if (source_whiteX == white_d65_X && source_whiteY == white_d65_Y && source_whiteZ == white_d65_Z) {
700 // early exit if noop
701 return;
702 }
703 // at first apply Bradford matrix
704 double rho_in = 0.8951000 * X + 0.2664000 * Y - 0.1614000 * Z;
705 double gamma_in = -0.7502000 * X + 1.7135000 * Y + 0.0367000 * Z;
706 double beta_in = 0.0389000 * X - 0.0685000 * Y + 1.0296000 * Z;
707
708 // apply a diagonal matrix with the diagonal entries being the inverse bradford-transformed white point
709 rho_in /= 0.8951000 * source_whiteX + 0.2664000 * source_whiteY - 0.1614000 * source_whiteZ;
710 gamma_in /= -0.7502000 * source_whiteX + 1.7135000 * source_whiteY + 0.0367000 * source_whiteZ;
711 beta_in /= 0.0389000 * source_whiteX - 0.0685000 * source_whiteY + 1.0296000 * source_whiteZ;
712
713 // now revert the two steps above, but substituting the source white point by the device white point (D65)
714 // Since the white point is known a priori this has been combined into a single operation.
715 X = 0.92918329 * rho_in - 0.15299782 * gamma_in + 0.17428453 * beta_in;
716 Y = 0.40698452 * rho_in + 0.53931108 * gamma_in + 0.05370440 * beta_in;
717 Z = -0.00802913 * rho_in + 0.04166125 * gamma_in + 1.05519788 * beta_in;
718}
719
720GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr, GfxState *state)
721{
722 GfxCalGrayColorSpace *cs;
723 Object obj1, obj2;
724
725 obj1 = arr->get(i: 1);
726 if (!obj1.isDict()) {
727 error(category: errSyntaxWarning, pos: -1, msg: "Bad CalGray color space");
728 return nullptr;
729 }
730 cs = new GfxCalGrayColorSpace();
731 obj2 = obj1.dictLookup(key: "WhitePoint");
732 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
733 cs->whiteX = obj2.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 1);
734 cs->whiteY = obj2.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 1);
735 cs->whiteZ = obj2.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 1);
736 }
737 obj2 = obj1.dictLookup(key: "BlackPoint");
738 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
739 cs->blackX = obj2.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
740 cs->blackY = obj2.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
741 cs->blackZ = obj2.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 0);
742 }
743
744 cs->gamma = obj1.dictLookup(key: "Gamma").getNumWithDefaultValue(defaultValue: 1);
745
746#ifdef USE_CMS
747 cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr;
748#endif
749 return cs;
750}
751
752// convert CalGray to media XYZ color space
753// (not multiply by the white point)
754void GfxCalGrayColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const
755{
756 const double A = colToDbl(x: color->c[0]);
757 const double xyzColor = pow(x: A, y: gamma);
758 *pX = xyzColor;
759 *pY = xyzColor;
760 *pZ = xyzColor;
761}
762
763void GfxCalGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
764{
765 GfxRGB rgb;
766
767#ifdef USE_CMS
768 if (transform && transform->getTransformPixelType() == PT_GRAY) {
769 unsigned char out[gfxColorMaxComps];
770 double in[gfxColorMaxComps];
771 double X, Y, Z;
772
773 getXYZ(color, &X, &Y, &Z);
774 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
775 in[0] = X;
776 in[1] = Y;
777 in[2] = Z;
778 transform->doTransform(in, out, 1);
779 *gray = byteToCol(out[0]);
780 return;
781 }
782#endif
783 getRGB(color, rgb: &rgb);
784 *gray = clip01(x: (GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5));
785}
786
787void GfxCalGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
788{
789 double X, Y, Z;
790 double r, g, b;
791
792 getXYZ(color, pX: &X, pY: &Y, pZ: &Z);
793#ifdef USE_CMS
794 if (transform && transform->getTransformPixelType() == PT_RGB) {
795 unsigned char out[gfxColorMaxComps];
796 double in[gfxColorMaxComps];
797
798 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
799 in[0] = X;
800 in[1] = Y;
801 in[2] = Z;
802 transform->doTransform(in, out, 1);
803 rgb->r = byteToCol(out[0]);
804 rgb->g = byteToCol(out[1]);
805 rgb->b = byteToCol(out[2]);
806 return;
807 }
808#endif
809 bradford_transform_to_d65(X, Y, Z, source_whiteX: whiteX, source_whiteY: whiteY, source_whiteZ: whiteZ);
810 // convert XYZ to RGB, including gamut mapping and gamma correction
811 r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z;
812 g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z;
813 b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z;
814 rgb->r = dblToCol(x: srgb_gamma_function(x: clip01(x: r)));
815 rgb->g = dblToCol(x: srgb_gamma_function(x: clip01(x: g)));
816 rgb->b = dblToCol(x: srgb_gamma_function(x: clip01(x: b)));
817}
818
819void GfxCalGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
820{
821 GfxRGB rgb;
822 GfxColorComp c, m, y, k;
823
824#ifdef USE_CMS
825 if (transform && transform->getTransformPixelType() == PT_CMYK) {
826 double in[gfxColorMaxComps];
827 unsigned char out[gfxColorMaxComps];
828 double X, Y, Z;
829
830 getXYZ(color, &X, &Y, &Z);
831 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
832 in[0] = X;
833 in[1] = Y;
834 in[2] = Z;
835 transform->doTransform(in, out, 1);
836 cmyk->c = byteToCol(out[0]);
837 cmyk->m = byteToCol(out[1]);
838 cmyk->y = byteToCol(out[2]);
839 cmyk->k = byteToCol(out[3]);
840 return;
841 }
842#endif
843 getRGB(color, rgb: &rgb);
844 c = clip01(gfxColorComp1 - rgb.r);
845 m = clip01(gfxColorComp1 - rgb.g);
846 y = clip01(gfxColorComp1 - rgb.b);
847 k = c;
848 if (m < k) {
849 k = m;
850 }
851 if (y < k) {
852 k = y;
853 }
854 cmyk->c = c - k;
855 cmyk->m = m - k;
856 cmyk->y = y - k;
857 cmyk->k = k;
858}
859
860void GfxCalGrayColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
861{
862 GfxCMYK cmyk;
863 clearGfxColor(gfxColor: deviceN);
864 getCMYK(color, cmyk: &cmyk);
865 deviceN->c[0] = cmyk.c;
866 deviceN->c[1] = cmyk.m;
867 deviceN->c[2] = cmyk.y;
868 deviceN->c[3] = cmyk.k;
869}
870
871void GfxCalGrayColorSpace::getDefaultColor(GfxColor *color) const
872{
873 color->c[0] = 0;
874}
875
876//------------------------------------------------------------------------
877// GfxDeviceRGBColorSpace
878//------------------------------------------------------------------------
879
880GfxDeviceRGBColorSpace::GfxDeviceRGBColorSpace() { }
881
882GfxDeviceRGBColorSpace::~GfxDeviceRGBColorSpace() { }
883
884GfxColorSpace *GfxDeviceRGBColorSpace::copy() const
885{
886 return new GfxDeviceRGBColorSpace();
887}
888
889void GfxDeviceRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
890{
891 *gray = clip01(x: (GfxColorComp)(0.3 * color->c[0] + 0.59 * color->c[1] + 0.11 * color->c[2] + 0.5));
892}
893
894void GfxDeviceRGBColorSpace::getGrayLine(unsigned char *in, unsigned char *out, int length)
895{
896 int i;
897
898 for (i = 0; i < length; i++) {
899 out[i] = (in[i * 3 + 0] * 19595 + in[i * 3 + 1] * 38469 + in[i * 3 + 2] * 7472) / 65536;
900 }
901}
902
903void GfxDeviceRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
904{
905 rgb->r = clip01(x: color->c[0]);
906 rgb->g = clip01(x: color->c[1]);
907 rgb->b = clip01(x: color->c[2]);
908}
909
910void GfxDeviceRGBColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
911{
912 unsigned char *p;
913 int i;
914
915 for (i = 0, p = in; i < length; i++, p += 3) {
916 out[i] = (p[0] << 16) | (p[1] << 8) | (p[2] << 0);
917 }
918}
919
920void GfxDeviceRGBColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
921{
922 for (int i = 0; i < length; i++) {
923 *out++ = *in++;
924 *out++ = *in++;
925 *out++ = *in++;
926 }
927}
928
929void GfxDeviceRGBColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
930{
931 for (int i = 0; i < length; i++) {
932 *out++ = *in++;
933 *out++ = *in++;
934 *out++ = *in++;
935 *out++ = 255;
936 }
937}
938
939void GfxDeviceRGBColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
940{
941 GfxColorComp c, m, y, k;
942
943 for (int i = 0; i < length; i++) {
944 c = byteToCol(x: 255 - *in++);
945 m = byteToCol(x: 255 - *in++);
946 y = byteToCol(x: 255 - *in++);
947 k = c;
948 if (m < k) {
949 k = m;
950 }
951 if (y < k) {
952 k = y;
953 }
954 *out++ = colToByte(x: c - k);
955 *out++ = colToByte(x: m - k);
956 *out++ = colToByte(x: y - k);
957 *out++ = colToByte(x: k);
958 }
959}
960
961void GfxDeviceRGBColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
962{
963 GfxColorComp c, m, y, k;
964
965 for (int i = 0; i < length; i++) {
966 for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
967 out[j] = 0;
968 }
969 c = byteToCol(x: 255 - *in++);
970 m = byteToCol(x: 255 - *in++);
971 y = byteToCol(x: 255 - *in++);
972 k = c;
973 if (m < k) {
974 k = m;
975 }
976 if (y < k) {
977 k = y;
978 }
979 out[0] = colToByte(x: c - k);
980 out[1] = colToByte(x: m - k);
981 out[2] = colToByte(x: y - k);
982 out[3] = colToByte(x: k);
983 out += (SPOT_NCOMPS + 4);
984 }
985}
986
987void GfxDeviceRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
988{
989 GfxColorComp c, m, y, k;
990
991 c = clip01(gfxColorComp1 - color->c[0]);
992 m = clip01(gfxColorComp1 - color->c[1]);
993 y = clip01(gfxColorComp1 - color->c[2]);
994 k = c;
995 if (m < k) {
996 k = m;
997 }
998 if (y < k) {
999 k = y;
1000 }
1001 cmyk->c = c - k;
1002 cmyk->m = m - k;
1003 cmyk->y = y - k;
1004 cmyk->k = k;
1005}
1006
1007void GfxDeviceRGBColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
1008{
1009 GfxCMYK cmyk;
1010 clearGfxColor(gfxColor: deviceN);
1011 getCMYK(color, cmyk: &cmyk);
1012 deviceN->c[0] = cmyk.c;
1013 deviceN->c[1] = cmyk.m;
1014 deviceN->c[2] = cmyk.y;
1015 deviceN->c[3] = cmyk.k;
1016}
1017
1018void GfxDeviceRGBColorSpace::getDefaultColor(GfxColor *color) const
1019{
1020 color->c[0] = 0;
1021 color->c[1] = 0;
1022 color->c[2] = 0;
1023}
1024
1025//------------------------------------------------------------------------
1026// GfxCalRGBColorSpace
1027//------------------------------------------------------------------------
1028
1029GfxCalRGBColorSpace::GfxCalRGBColorSpace()
1030{
1031 whiteX = whiteY = whiteZ = 1;
1032 blackX = blackY = blackZ = 0;
1033 gammaR = gammaG = gammaB = 1;
1034 mat[0] = 1;
1035 mat[1] = 0;
1036 mat[2] = 0;
1037 mat[3] = 0;
1038 mat[4] = 1;
1039 mat[5] = 0;
1040 mat[6] = 0;
1041 mat[7] = 0;
1042 mat[8] = 1;
1043}
1044
1045GfxCalRGBColorSpace::~GfxCalRGBColorSpace() { }
1046
1047GfxColorSpace *GfxCalRGBColorSpace::copy() const
1048{
1049 GfxCalRGBColorSpace *cs;
1050 int i;
1051
1052 cs = new GfxCalRGBColorSpace();
1053 cs->whiteX = whiteX;
1054 cs->whiteY = whiteY;
1055 cs->whiteZ = whiteZ;
1056 cs->blackX = blackX;
1057 cs->blackY = blackY;
1058 cs->blackZ = blackZ;
1059 cs->gammaR = gammaR;
1060 cs->gammaG = gammaG;
1061 cs->gammaB = gammaB;
1062 for (i = 0; i < 9; ++i) {
1063 cs->mat[i] = mat[i];
1064 }
1065#ifdef USE_CMS
1066 cs->transform = transform;
1067#endif
1068 return cs;
1069}
1070
1071GfxColorSpace *GfxCalRGBColorSpace::parse(Array *arr, GfxState *state)
1072{
1073 GfxCalRGBColorSpace *cs;
1074 Object obj1, obj2;
1075 int i;
1076
1077 obj1 = arr->get(i: 1);
1078 if (!obj1.isDict()) {
1079 error(category: errSyntaxWarning, pos: -1, msg: "Bad CalRGB color space");
1080 return nullptr;
1081 }
1082 cs = new GfxCalRGBColorSpace();
1083 obj2 = obj1.dictLookup(key: "WhitePoint");
1084 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
1085 cs->whiteX = obj2.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 1);
1086 cs->whiteY = obj2.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 1);
1087 cs->whiteZ = obj2.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 1);
1088 }
1089 obj2 = obj1.dictLookup(key: "BlackPoint");
1090 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
1091 cs->blackX = obj2.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
1092 cs->blackY = obj2.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
1093 cs->blackZ = obj2.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 0);
1094 }
1095 obj2 = obj1.dictLookup(key: "Gamma");
1096 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
1097 cs->gammaR = obj2.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 1);
1098 cs->gammaG = obj2.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 1);
1099 cs->gammaB = obj2.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 1);
1100 }
1101 obj2 = obj1.dictLookup(key: "Matrix");
1102 if (obj2.isArray() && obj2.arrayGetLength() == 9) {
1103 for (i = 0; i < 9; ++i) {
1104 Object obj3 = obj2.arrayGet(i);
1105 if (likely(obj3.isNum())) {
1106 cs->mat[i] = obj3.getNum();
1107 }
1108 }
1109 }
1110
1111#ifdef USE_CMS
1112 cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr;
1113#endif
1114 return cs;
1115}
1116
1117// convert CalRGB to XYZ color space
1118void GfxCalRGBColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const
1119{
1120 double A, B, C;
1121
1122 A = pow(x: colToDbl(x: color->c[0]), y: gammaR);
1123 B = pow(x: colToDbl(x: color->c[1]), y: gammaG);
1124 C = pow(x: colToDbl(x: color->c[2]), y: gammaB);
1125 *pX = mat[0] * A + mat[3] * B + mat[6] * C;
1126 *pY = mat[1] * A + mat[4] * B + mat[7] * C;
1127 *pZ = mat[2] * A + mat[5] * B + mat[8] * C;
1128}
1129
1130void GfxCalRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
1131{
1132 GfxRGB rgb;
1133
1134#ifdef USE_CMS
1135 if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
1136 unsigned char out[gfxColorMaxComps];
1137 double in[gfxColorMaxComps];
1138 double X, Y, Z;
1139
1140 getXYZ(color, &X, &Y, &Z);
1141 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
1142 in[0] = X;
1143 in[1] = Y;
1144 in[2] = Z;
1145 transform->doTransform(in, out, 1);
1146 *gray = byteToCol(out[0]);
1147 return;
1148 }
1149#endif
1150 getRGB(color, rgb: &rgb);
1151 *gray = clip01(x: (GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5));
1152}
1153
1154void GfxCalRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
1155{
1156 double X, Y, Z;
1157 double r, g, b;
1158
1159 getXYZ(color, pX: &X, pY: &Y, pZ: &Z);
1160#ifdef USE_CMS
1161 if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
1162 unsigned char out[gfxColorMaxComps];
1163 double in[gfxColorMaxComps];
1164
1165 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
1166 in[0] = X;
1167 in[1] = Y;
1168 in[2] = Z;
1169 transform->doTransform(in, out, 1);
1170 rgb->r = byteToCol(out[0]);
1171 rgb->g = byteToCol(out[1]);
1172 rgb->b = byteToCol(out[2]);
1173
1174 return;
1175 }
1176#endif
1177 bradford_transform_to_d65(X, Y, Z, source_whiteX: whiteX, source_whiteY: whiteY, source_whiteZ: whiteZ);
1178 // convert XYZ to RGB, including gamut mapping and gamma correction
1179 r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z;
1180 g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z;
1181 b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z;
1182 rgb->r = dblToCol(x: srgb_gamma_function(x: clip01(x: r)));
1183 rgb->g = dblToCol(x: srgb_gamma_function(x: clip01(x: g)));
1184 rgb->b = dblToCol(x: srgb_gamma_function(x: clip01(x: b)));
1185}
1186
1187void GfxCalRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
1188{
1189 GfxRGB rgb;
1190 GfxColorComp c, m, y, k;
1191
1192#ifdef USE_CMS
1193 if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
1194 double in[gfxColorMaxComps];
1195 unsigned char out[gfxColorMaxComps];
1196 double X, Y, Z;
1197
1198 getXYZ(color, &X, &Y, &Z);
1199 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
1200 in[0] = X;
1201 in[1] = Y;
1202 in[2] = Z;
1203 transform->doTransform(in, out, 1);
1204 cmyk->c = byteToCol(out[0]);
1205 cmyk->m = byteToCol(out[1]);
1206 cmyk->y = byteToCol(out[2]);
1207 cmyk->k = byteToCol(out[3]);
1208 return;
1209 }
1210#endif
1211 getRGB(color, rgb: &rgb);
1212 c = clip01(gfxColorComp1 - rgb.r);
1213 m = clip01(gfxColorComp1 - rgb.g);
1214 y = clip01(gfxColorComp1 - rgb.b);
1215 k = c;
1216 if (m < k) {
1217 k = m;
1218 }
1219 if (y < k) {
1220 k = y;
1221 }
1222 cmyk->c = c - k;
1223 cmyk->m = m - k;
1224 cmyk->y = y - k;
1225 cmyk->k = k;
1226}
1227
1228void GfxCalRGBColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
1229{
1230 GfxCMYK cmyk;
1231 clearGfxColor(gfxColor: deviceN);
1232 getCMYK(color, cmyk: &cmyk);
1233 deviceN->c[0] = cmyk.c;
1234 deviceN->c[1] = cmyk.m;
1235 deviceN->c[2] = cmyk.y;
1236 deviceN->c[3] = cmyk.k;
1237}
1238
1239void GfxCalRGBColorSpace::getDefaultColor(GfxColor *color) const
1240{
1241 color->c[0] = 0;
1242 color->c[1] = 0;
1243 color->c[2] = 0;
1244}
1245
1246//------------------------------------------------------------------------
1247// GfxDeviceCMYKColorSpace
1248//------------------------------------------------------------------------
1249
1250GfxDeviceCMYKColorSpace::GfxDeviceCMYKColorSpace() { }
1251
1252GfxDeviceCMYKColorSpace::~GfxDeviceCMYKColorSpace() { }
1253
1254GfxColorSpace *GfxDeviceCMYKColorSpace::copy() const
1255{
1256 return new GfxDeviceCMYKColorSpace();
1257}
1258
1259void GfxDeviceCMYKColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
1260{
1261 *gray = clip01(x: (GfxColorComp)(gfxColorComp1 - color->c[3] - 0.3 * color->c[0] - 0.59 * color->c[1] - 0.11 * color->c[2] + 0.5));
1262}
1263
1264void GfxDeviceCMYKColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
1265{
1266 double c, m, y, k, c1, m1, y1, k1, r, g, b;
1267
1268 c = colToDbl(x: color->c[0]);
1269 m = colToDbl(x: color->c[1]);
1270 y = colToDbl(x: color->c[2]);
1271 k = colToDbl(x: color->c[3]);
1272 c1 = 1 - c;
1273 m1 = 1 - m;
1274 y1 = 1 - y;
1275 k1 = 1 - k;
1276 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
1277 rgb->r = clip01(x: dblToCol(x: r));
1278 rgb->g = clip01(x: dblToCol(x: g));
1279 rgb->b = clip01(x: dblToCol(x: b));
1280}
1281
1282static inline void GfxDeviceCMYKColorSpacegetRGBLineHelper(unsigned char *&in, double &r, double &g, double &b)
1283{
1284 double c, m, y, k, c1, m1, y1, k1;
1285
1286 c = byteToDbl(x: *in++);
1287 m = byteToDbl(x: *in++);
1288 y = byteToDbl(x: *in++);
1289 k = byteToDbl(x: *in++);
1290 c1 = 1 - c;
1291 m1 = 1 - m;
1292 y1 = 1 - y;
1293 k1 = 1 - k;
1294 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
1295}
1296
1297void GfxDeviceCMYKColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
1298{
1299 double r, g, b;
1300 for (int i = 0; i < length; i++) {
1301 GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
1302 *out++ = (dblToByte(x: clip01(x: r)) << 16) | (dblToByte(x: clip01(x: g)) << 8) | dblToByte(x: clip01(x: b));
1303 }
1304}
1305
1306void GfxDeviceCMYKColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
1307{
1308 double r, g, b;
1309
1310 for (int i = 0; i < length; i++) {
1311 GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
1312 *out++ = dblToByte(x: clip01(x: r));
1313 *out++ = dblToByte(x: clip01(x: g));
1314 *out++ = dblToByte(x: clip01(x: b));
1315 }
1316}
1317
1318void GfxDeviceCMYKColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
1319{
1320 double r, g, b;
1321
1322 for (int i = 0; i < length; i++) {
1323 GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
1324 *out++ = dblToByte(x: clip01(x: r));
1325 *out++ = dblToByte(x: clip01(x: g));
1326 *out++ = dblToByte(x: clip01(x: b));
1327 *out++ = 255;
1328 }
1329}
1330
1331void GfxDeviceCMYKColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
1332{
1333 for (int i = 0; i < length; i++) {
1334 *out++ = *in++;
1335 *out++ = *in++;
1336 *out++ = *in++;
1337 *out++ = *in++;
1338 }
1339}
1340
1341void GfxDeviceCMYKColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
1342{
1343 for (int i = 0; i < length; i++) {
1344 for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
1345 out[j] = 0;
1346 }
1347 out[0] = *in++;
1348 out[1] = *in++;
1349 out[2] = *in++;
1350 out[3] = *in++;
1351 out += (SPOT_NCOMPS + 4);
1352 }
1353}
1354
1355void GfxDeviceCMYKColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
1356{
1357 cmyk->c = clip01(x: color->c[0]);
1358 cmyk->m = clip01(x: color->c[1]);
1359 cmyk->y = clip01(x: color->c[2]);
1360 cmyk->k = clip01(x: color->c[3]);
1361}
1362
1363void GfxDeviceCMYKColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
1364{
1365 clearGfxColor(gfxColor: deviceN);
1366 deviceN->c[0] = clip01(x: color->c[0]);
1367 deviceN->c[1] = clip01(x: color->c[1]);
1368 deviceN->c[2] = clip01(x: color->c[2]);
1369 deviceN->c[3] = clip01(x: color->c[3]);
1370}
1371
1372void GfxDeviceCMYKColorSpace::getDefaultColor(GfxColor *color) const
1373{
1374 color->c[0] = 0;
1375 color->c[1] = 0;
1376 color->c[2] = 0;
1377 color->c[3] = gfxColorComp1;
1378}
1379
1380//------------------------------------------------------------------------
1381// GfxLabColorSpace
1382//------------------------------------------------------------------------
1383
1384GfxLabColorSpace::GfxLabColorSpace()
1385{
1386 whiteX = whiteY = whiteZ = 1;
1387 blackX = blackY = blackZ = 0;
1388 aMin = bMin = -100;
1389 aMax = bMax = 100;
1390}
1391
1392GfxLabColorSpace::~GfxLabColorSpace() { }
1393
1394GfxColorSpace *GfxLabColorSpace::copy() const
1395{
1396 GfxLabColorSpace *cs;
1397
1398 cs = new GfxLabColorSpace();
1399 cs->whiteX = whiteX;
1400 cs->whiteY = whiteY;
1401 cs->whiteZ = whiteZ;
1402 cs->blackX = blackX;
1403 cs->blackY = blackY;
1404 cs->blackZ = blackZ;
1405 cs->aMin = aMin;
1406 cs->aMax = aMax;
1407 cs->bMin = bMin;
1408 cs->bMax = bMax;
1409#ifdef USE_CMS
1410 cs->transform = transform;
1411#endif
1412 return cs;
1413}
1414
1415GfxColorSpace *GfxLabColorSpace::parse(Array *arr, GfxState *state)
1416{
1417 GfxLabColorSpace *cs;
1418 Object obj1, obj2;
1419
1420 obj1 = arr->get(i: 1);
1421 if (!obj1.isDict()) {
1422 error(category: errSyntaxWarning, pos: -1, msg: "Bad Lab color space");
1423 return nullptr;
1424 }
1425 cs = new GfxLabColorSpace();
1426 bool ok = true;
1427 obj2 = obj1.dictLookup(key: "WhitePoint");
1428 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
1429 cs->whiteX = obj2.arrayGet(i: 0).getNum(ok: &ok);
1430 cs->whiteY = obj2.arrayGet(i: 1).getNum(ok: &ok);
1431 cs->whiteZ = obj2.arrayGet(i: 2).getNum(ok: &ok);
1432 }
1433 obj2 = obj1.dictLookup(key: "BlackPoint");
1434 if (obj2.isArray() && obj2.arrayGetLength() == 3) {
1435 cs->blackX = obj2.arrayGet(i: 0).getNum(ok: &ok);
1436 cs->blackY = obj2.arrayGet(i: 1).getNum(ok: &ok);
1437 cs->blackZ = obj2.arrayGet(i: 2).getNum(ok: &ok);
1438 }
1439 obj2 = obj1.dictLookup(key: "Range");
1440 if (obj2.isArray() && obj2.arrayGetLength() == 4) {
1441 cs->aMin = obj2.arrayGet(i: 0).getNum(ok: &ok);
1442 cs->aMax = obj2.arrayGet(i: 1).getNum(ok: &ok);
1443 cs->bMin = obj2.arrayGet(i: 2).getNum(ok: &ok);
1444 cs->bMax = obj2.arrayGet(i: 3).getNum(ok: &ok);
1445 }
1446
1447 if (!ok) {
1448 error(category: errSyntaxWarning, pos: -1, msg: "Bad Lab color space");
1449#ifdef USE_CMS
1450 cs->transform = nullptr;
1451#endif
1452 delete cs;
1453 return nullptr;
1454 }
1455
1456#ifdef USE_CMS
1457 cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : nullptr;
1458#endif
1459 return cs;
1460}
1461
1462void GfxLabColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
1463{
1464 GfxRGB rgb;
1465
1466#ifdef USE_CMS
1467 if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
1468 unsigned char out[gfxColorMaxComps];
1469 double in[gfxColorMaxComps];
1470
1471 getXYZ(color, &in[0], &in[1], &in[2]);
1472 bradford_transform_to_d50(in[0], in[1], in[2], whiteX, whiteY, whiteZ);
1473 transform->doTransform(in, out, 1);
1474 *gray = byteToCol(out[0]);
1475 return;
1476 }
1477#endif
1478 getRGB(color, rgb: &rgb);
1479 *gray = clip01(x: (GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b + 0.5));
1480}
1481
1482// convert L*a*b* to media XYZ color space
1483// (not multiply by the white point)
1484void GfxLabColorSpace::getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const
1485{
1486 double X, Y, Z;
1487 double t1, t2;
1488
1489 t1 = (colToDbl(x: color->c[0]) + 16) / 116;
1490 t2 = t1 + colToDbl(x: color->c[1]) / 500;
1491 if (t2 >= (6.0 / 29.0)) {
1492 X = t2 * t2 * t2;
1493 } else {
1494 X = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
1495 }
1496 if (t1 >= (6.0 / 29.0)) {
1497 Y = t1 * t1 * t1;
1498 } else {
1499 Y = (108.0 / 841.0) * (t1 - (4.0 / 29.0));
1500 }
1501 t2 = t1 - colToDbl(x: color->c[2]) / 200;
1502 if (t2 >= (6.0 / 29.0)) {
1503 Z = t2 * t2 * t2;
1504 } else {
1505 Z = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
1506 }
1507 *pX = X;
1508 *pY = Y;
1509 *pZ = Z;
1510}
1511
1512void GfxLabColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
1513{
1514 double X, Y, Z;
1515
1516 getXYZ(color, pX: &X, pY: &Y, pZ: &Z);
1517 X *= whiteX;
1518 Y *= whiteY;
1519 Z *= whiteZ;
1520#ifdef USE_CMS
1521 if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
1522 unsigned char out[gfxColorMaxComps];
1523 double in[gfxColorMaxComps];
1524
1525 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
1526 in[0] = X;
1527 in[1] = Y;
1528 in[2] = Z;
1529 transform->doTransform(in, out, 1);
1530 rgb->r = byteToCol(out[0]);
1531 rgb->g = byteToCol(out[1]);
1532 rgb->b = byteToCol(out[2]);
1533 return;
1534 } else if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
1535 unsigned char out[gfxColorMaxComps];
1536 double in[gfxColorMaxComps];
1537 double c, m, y, k, c1, m1, y1, k1, r, g, b;
1538
1539 bradford_transform_to_d50(X, Y, Z, whiteX, whiteY, whiteZ);
1540 in[0] = X;
1541 in[1] = Y;
1542 in[2] = Z;
1543 transform->doTransform(in, out, 1);
1544 c = byteToDbl(out[0]);
1545 m = byteToDbl(out[1]);
1546 y = byteToDbl(out[2]);
1547 k = byteToDbl(out[3]);
1548 c1 = 1 - c;
1549 m1 = 1 - m;
1550 y1 = 1 - y;
1551 k1 = 1 - k;
1552 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
1553 rgb->r = clip01(dblToCol(r));
1554 rgb->g = clip01(dblToCol(g));
1555 rgb->b = clip01(dblToCol(b));
1556 return;
1557 }
1558#endif
1559 bradford_transform_to_d65(X, Y, Z, source_whiteX: whiteX, source_whiteY: whiteY, source_whiteZ: whiteZ);
1560 // convert XYZ to RGB, including gamut mapping and gamma correction
1561 const double r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z;
1562 const double g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z;
1563 const double b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z;
1564 rgb->r = dblToCol(x: srgb_gamma_function(x: clip01(x: r)));
1565 rgb->g = dblToCol(x: srgb_gamma_function(x: clip01(x: g)));
1566 rgb->b = dblToCol(x: srgb_gamma_function(x: clip01(x: b)));
1567}
1568
1569void GfxLabColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
1570{
1571 GfxRGB rgb;
1572 GfxColorComp c, m, y, k;
1573
1574#ifdef USE_CMS
1575 if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
1576 double in[gfxColorMaxComps];
1577 unsigned char out[gfxColorMaxComps];
1578
1579 getXYZ(color, &in[0], &in[1], &in[2]);
1580 bradford_transform_to_d50(in[0], in[1], in[2], whiteX, whiteY, whiteZ);
1581 transform->doTransform(in, out, 1);
1582 cmyk->c = byteToCol(out[0]);
1583 cmyk->m = byteToCol(out[1]);
1584 cmyk->y = byteToCol(out[2]);
1585 cmyk->k = byteToCol(out[3]);
1586 return;
1587 }
1588#endif
1589 getRGB(color, rgb: &rgb);
1590 c = clip01(gfxColorComp1 - rgb.r);
1591 m = clip01(gfxColorComp1 - rgb.g);
1592 y = clip01(gfxColorComp1 - rgb.b);
1593 k = c;
1594 if (m < k) {
1595 k = m;
1596 }
1597 if (y < k) {
1598 k = y;
1599 }
1600 cmyk->c = c - k;
1601 cmyk->m = m - k;
1602 cmyk->y = y - k;
1603 cmyk->k = k;
1604}
1605
1606void GfxLabColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
1607{
1608 GfxCMYK cmyk;
1609 clearGfxColor(gfxColor: deviceN);
1610 getCMYK(color, cmyk: &cmyk);
1611 deviceN->c[0] = cmyk.c;
1612 deviceN->c[1] = cmyk.m;
1613 deviceN->c[2] = cmyk.y;
1614 deviceN->c[3] = cmyk.k;
1615}
1616
1617void GfxLabColorSpace::getDefaultColor(GfxColor *color) const
1618{
1619 color->c[0] = 0;
1620 if (aMin > 0) {
1621 color->c[1] = dblToCol(x: aMin);
1622 } else if (aMax < 0) {
1623 color->c[1] = dblToCol(x: aMax);
1624 } else {
1625 color->c[1] = 0;
1626 }
1627 if (bMin > 0) {
1628 color->c[2] = dblToCol(x: bMin);
1629 } else if (bMax < 0) {
1630 color->c[2] = dblToCol(x: bMax);
1631 } else {
1632 color->c[2] = 0;
1633 }
1634}
1635
1636void GfxLabColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
1637{
1638 decodeLow[0] = 0;
1639 decodeRange[0] = 100;
1640 decodeLow[1] = aMin;
1641 decodeRange[1] = aMax - aMin;
1642 decodeLow[2] = bMin;
1643 decodeRange[2] = bMax - bMin;
1644}
1645
1646//------------------------------------------------------------------------
1647// GfxICCBasedColorSpace
1648//------------------------------------------------------------------------
1649
1650GfxICCBasedColorSpace::GfxICCBasedColorSpace(int nCompsA, GfxColorSpace *altA, const Ref *iccProfileStreamA)
1651{
1652 nComps = nCompsA;
1653 alt = altA;
1654 iccProfileStream = *iccProfileStreamA;
1655 rangeMin[0] = rangeMin[1] = rangeMin[2] = rangeMin[3] = 0;
1656 rangeMax[0] = rangeMax[1] = rangeMax[2] = rangeMax[3] = 1;
1657#ifdef USE_CMS
1658 transform = nullptr;
1659 lineTransform = nullptr;
1660 psCSA = nullptr;
1661#endif
1662}
1663
1664GfxICCBasedColorSpace::~GfxICCBasedColorSpace()
1665{
1666 delete alt;
1667#ifdef USE_CMS
1668 if (psCSA) {
1669 gfree(psCSA);
1670 }
1671#endif
1672}
1673
1674GfxColorSpace *GfxICCBasedColorSpace::copy() const
1675{
1676 GfxICCBasedColorSpace *cs;
1677 int i;
1678
1679 cs = new GfxICCBasedColorSpace(nComps, alt->copy(), &iccProfileStream);
1680 for (i = 0; i < 4; ++i) {
1681 cs->rangeMin[i] = rangeMin[i];
1682 cs->rangeMax[i] = rangeMax[i];
1683 }
1684#ifdef USE_CMS
1685 cs->profile = profile;
1686 cs->transform = transform;
1687 cs->lineTransform = lineTransform;
1688#endif
1689 return cs;
1690}
1691
1692GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, GfxState *state, int recursion)
1693{
1694 GfxICCBasedColorSpace *cs;
1695 int nCompsA;
1696 GfxColorSpace *altA;
1697 Dict *dict;
1698 Object obj1, obj2;
1699 int i;
1700
1701 if (arr->getLength() < 2) {
1702 error(category: errSyntaxError, pos: -1, msg: "Bad ICCBased color space");
1703 return nullptr;
1704 }
1705 const Object &obj1Ref = arr->getNF(i: 1);
1706 const Ref iccProfileStreamA = obj1Ref.isRef() ? obj1Ref.getRef() : Ref::INVALID();
1707#ifdef USE_CMS
1708 // check cache
1709 if (out && iccProfileStreamA != Ref::INVALID()) {
1710 if (auto *item = out->getIccColorSpaceCache()->lookup(iccProfileStreamA)) {
1711 cs = static_cast<GfxICCBasedColorSpace *>(item->copy());
1712 int transformIntent = cs->getIntent();
1713 int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
1714 if (state != nullptr) {
1715 cmsIntent = state->getCmsRenderingIntent();
1716 }
1717 if (transformIntent == cmsIntent) {
1718 return cs;
1719 }
1720 delete cs;
1721 }
1722 }
1723#endif
1724 obj1 = arr->get(i: 1);
1725 if (!obj1.isStream()) {
1726 error(category: errSyntaxWarning, pos: -1, msg: "Bad ICCBased color space (stream)");
1727 return nullptr;
1728 }
1729 dict = obj1.streamGetDict();
1730 obj2 = dict->lookup(key: "N");
1731 if (!obj2.isInt()) {
1732 error(category: errSyntaxWarning, pos: -1, msg: "Bad ICCBased color space (N)");
1733 return nullptr;
1734 }
1735 nCompsA = obj2.getInt();
1736 if (nCompsA > 4) {
1737 error(category: errSyntaxError, pos: -1, msg: "ICCBased color space with too many ({0:d} > 4) components", nCompsA);
1738 nCompsA = 4;
1739 }
1740 obj2 = dict->lookup(key: "Alternate");
1741 if (obj2.isNull() || !(altA = GfxColorSpace::parse(res: nullptr, csObj: &obj2, out, state, recursion: recursion + 1))) {
1742 switch (nCompsA) {
1743 case 1:
1744 altA = new GfxDeviceGrayColorSpace();
1745 break;
1746 case 3:
1747 altA = new GfxDeviceRGBColorSpace();
1748 break;
1749 case 4:
1750 altA = new GfxDeviceCMYKColorSpace();
1751 break;
1752 default:
1753 error(category: errSyntaxWarning, pos: -1, msg: "Bad ICCBased color space - invalid N");
1754 return nullptr;
1755 }
1756 }
1757 if (altA->getNComps() != nCompsA) {
1758 error(category: errSyntaxWarning, pos: -1, msg: "Bad ICCBased color space - N doesn't match alt color space");
1759 delete altA;
1760 return nullptr;
1761 }
1762 cs = new GfxICCBasedColorSpace(nCompsA, altA, &iccProfileStreamA);
1763 obj2 = dict->lookup(key: "Range");
1764 if (obj2.isArray() && obj2.arrayGetLength() == 2 * nCompsA) {
1765 for (i = 0; i < nCompsA; ++i) {
1766 cs->rangeMin[i] = obj2.arrayGet(i: 2 * i).getNumWithDefaultValue(defaultValue: 0);
1767 cs->rangeMax[i] = obj2.arrayGet(i: 2 * i + 1).getNumWithDefaultValue(defaultValue: 1);
1768 }
1769 }
1770
1771#ifdef USE_CMS
1772 obj1 = arr->get(1);
1773 if (!obj1.isStream()) {
1774 error(errSyntaxWarning, -1, "Bad ICCBased color space (stream)");
1775 delete cs;
1776 return nullptr;
1777 }
1778 Stream *iccStream = obj1.getStream();
1779
1780 const std::vector<unsigned char> profBuf = iccStream->toUnsignedChars(65536, 65536);
1781 auto hp = make_GfxLCMSProfilePtr(cmsOpenProfileFromMem(profBuf.data(), profBuf.size()));
1782 cs->profile = hp;
1783 if (!hp) {
1784 error(errSyntaxWarning, -1, "read ICCBased color space profile error");
1785 } else {
1786 cs->buildTransforms(state);
1787 }
1788 // put this colorSpace into cache
1789 if (out && iccProfileStreamA != Ref::INVALID()) {
1790 out->getIccColorSpaceCache()->put(iccProfileStreamA, static_cast<GfxICCBasedColorSpace *>(cs->copy()));
1791 }
1792#endif
1793 return cs;
1794}
1795
1796#ifdef USE_CMS
1797void GfxICCBasedColorSpace::buildTransforms(GfxState *state)
1798{
1799 auto dhp = (state != nullptr && state->getDisplayProfile() != nullptr) ? state->getDisplayProfile() : nullptr;
1800 if (!dhp) {
1801 dhp = GfxState::sRGBProfile;
1802 }
1803 unsigned int cst = getCMSColorSpaceType(cmsGetColorSpace(profile.get()));
1804 unsigned int dNChannels = getCMSNChannels(cmsGetColorSpace(dhp.get()));
1805 unsigned int dcst = getCMSColorSpaceType(cmsGetColorSpace(dhp.get()));
1806 cmsHTRANSFORM transformA;
1807
1808 int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
1809 if (state != nullptr) {
1810 cmsIntent = state->getCmsRenderingIntent();
1811 }
1812 if ((transformA = cmsCreateTransform(profile.get(), COLORSPACE_SH(cst) | CHANNELS_SH(nComps) | BYTES_SH(1), dhp.get(), COLORSPACE_SH(dcst) | CHANNELS_SH(dNChannels) | BYTES_SH(1), cmsIntent, LCMS_FLAGS)) == nullptr) {
1813 error(errSyntaxWarning, -1, "Can't create transform");
1814 transform = nullptr;
1815 } else {
1816 transform = std::make_shared<GfxColorTransform>(transformA, cmsIntent, cst, dcst);
1817 }
1818 if (dcst == PT_RGB || dcst == PT_CMYK) {
1819 // create line transform only when the display is RGB type color space
1820 if ((transformA = cmsCreateTransform(profile.get(), CHANNELS_SH(nComps) | BYTES_SH(1), dhp.get(), (dcst == PT_RGB) ? TYPE_RGB_8 : TYPE_CMYK_8, cmsIntent, LCMS_FLAGS)) == nullptr) {
1821 error(errSyntaxWarning, -1, "Can't create transform");
1822 lineTransform = nullptr;
1823 } else {
1824 lineTransform = std::make_shared<GfxColorTransform>(transformA, cmsIntent, cst, dcst);
1825 }
1826 }
1827}
1828#endif
1829
1830void GfxICCBasedColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
1831{
1832#ifdef USE_CMS
1833 if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
1834 unsigned char in[gfxColorMaxComps];
1835 unsigned char out[gfxColorMaxComps];
1836
1837 if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
1838 in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
1839 in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
1840 in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
1841 } else {
1842 for (int i = 0; i < nComps; i++) {
1843 in[i] = colToByte(color->c[i]);
1844 }
1845 }
1846 if (nComps <= 4) {
1847 unsigned int key = 0;
1848 for (int j = 0; j < nComps; j++) {
1849 key = (key << 8) + in[j];
1850 }
1851 std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
1852 if (it != cmsCache.end()) {
1853 unsigned int value = it->second;
1854 *gray = byteToCol(value & 0xff);
1855 return;
1856 }
1857 }
1858 transform->doTransform(in, out, 1);
1859 *gray = byteToCol(out[0]);
1860 if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
1861 unsigned int key = 0;
1862 for (int j = 0; j < nComps; j++) {
1863 key = (key << 8) + in[j];
1864 }
1865 unsigned int value = out[0];
1866 cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
1867 }
1868 } else {
1869 GfxRGB rgb;
1870 getRGB(color, &rgb);
1871 *gray = clip01((GfxColorComp)(0.3 * rgb.r + 0.59 * rgb.g + 0.11 * rgb.b + 0.5));
1872 }
1873#else
1874 alt->getGray(color, gray);
1875#endif
1876}
1877
1878void GfxICCBasedColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
1879{
1880#ifdef USE_CMS
1881 if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
1882 unsigned char in[gfxColorMaxComps];
1883 unsigned char out[gfxColorMaxComps];
1884
1885 if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
1886 in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
1887 in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
1888 in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
1889 } else {
1890 for (int i = 0; i < nComps; i++) {
1891 in[i] = colToByte(color->c[i]);
1892 }
1893 }
1894 if (nComps <= 4) {
1895 unsigned int key = 0;
1896 for (int j = 0; j < nComps; j++) {
1897 key = (key << 8) + in[j];
1898 }
1899 std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
1900 if (it != cmsCache.end()) {
1901 unsigned int value = it->second;
1902 rgb->r = byteToCol(value >> 16);
1903 rgb->g = byteToCol((value >> 8) & 0xff);
1904 rgb->b = byteToCol(value & 0xff);
1905 return;
1906 }
1907 }
1908 transform->doTransform(in, out, 1);
1909 rgb->r = byteToCol(out[0]);
1910 rgb->g = byteToCol(out[1]);
1911 rgb->b = byteToCol(out[2]);
1912 if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
1913 unsigned int key = 0;
1914 for (int j = 0; j < nComps; j++) {
1915 key = (key << 8) + in[j];
1916 }
1917 unsigned int value = (out[0] << 16) + (out[1] << 8) + out[2];
1918 cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
1919 }
1920 } else if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
1921 unsigned char in[gfxColorMaxComps];
1922 unsigned char out[gfxColorMaxComps];
1923 double c, m, y, k, c1, m1, y1, k1, r, g, b;
1924
1925 if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
1926 in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
1927 in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
1928 in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
1929 } else {
1930 for (int i = 0; i < nComps; i++) {
1931 in[i] = colToByte(color->c[i]);
1932 }
1933 }
1934 if (nComps <= 4) {
1935 unsigned int key = 0;
1936 for (int j = 0; j < nComps; j++) {
1937 key = (key << 8) + in[j];
1938 }
1939 std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
1940 if (it != cmsCache.end()) {
1941 unsigned int value = it->second;
1942 rgb->r = byteToCol(value >> 16);
1943 rgb->g = byteToCol((value >> 8) & 0xff);
1944 rgb->b = byteToCol(value & 0xff);
1945 return;
1946 }
1947 }
1948 transform->doTransform(in, out, 1);
1949 c = byteToDbl(out[0]);
1950 m = byteToDbl(out[1]);
1951 y = byteToDbl(out[2]);
1952 k = byteToDbl(out[3]);
1953 c1 = 1 - c;
1954 m1 = 1 - m;
1955 y1 = 1 - y;
1956 k1 = 1 - k;
1957 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
1958 rgb->r = clip01(dblToCol(r));
1959 rgb->g = clip01(dblToCol(g));
1960 rgb->b = clip01(dblToCol(b));
1961 if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
1962 unsigned int key = 0;
1963 for (int j = 0; j < nComps; j++) {
1964 key = (key << 8) + in[j];
1965 }
1966 unsigned int value = (dblToByte(r) << 16) + (dblToByte(g) << 8) + dblToByte(b);
1967 cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
1968 }
1969 } else {
1970 alt->getRGB(color, rgb);
1971 }
1972#else
1973 alt->getRGB(color, rgb);
1974#endif
1975}
1976
1977void GfxICCBasedColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
1978{
1979#ifdef USE_CMS
1980 if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
1981 unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
1982 lineTransform->doTransform(in, tmp, length);
1983 for (int i = 0; i < length; ++i) {
1984 unsigned char *current = tmp + (i * 3);
1985 out[i] = (current[0] << 16) | (current[1] << 8) | current[2];
1986 }
1987 gfree(tmp);
1988 } else {
1989 alt->getRGBLine(in, out, length);
1990 }
1991#else
1992 alt->getRGBLine(in, out, length);
1993#endif
1994}
1995
1996void GfxICCBasedColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
1997{
1998#ifdef USE_CMS
1999 if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
2000 unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
2001 lineTransform->doTransform(in, tmp, length);
2002 unsigned char *current = tmp;
2003 for (int i = 0; i < length; ++i) {
2004 *out++ = *current++;
2005 *out++ = *current++;
2006 *out++ = *current++;
2007 }
2008 gfree(tmp);
2009 } else if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
2010 unsigned char *tmp = (unsigned char *)gmallocn(4 * length, sizeof(unsigned char));
2011 lineTransform->doTransform(in, tmp, length);
2012 unsigned char *current = tmp;
2013 double c, m, y, k, c1, m1, y1, k1, r, g, b;
2014 for (int i = 0; i < length; ++i) {
2015 c = byteToDbl(*current++);
2016 m = byteToDbl(*current++);
2017 y = byteToDbl(*current++);
2018 k = byteToDbl(*current++);
2019 c1 = 1 - c;
2020 m1 = 1 - m;
2021 y1 = 1 - y;
2022 k1 = 1 - k;
2023 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
2024 *out++ = dblToByte(r);
2025 *out++ = dblToByte(g);
2026 *out++ = dblToByte(b);
2027 }
2028 gfree(tmp);
2029 } else {
2030 alt->getRGBLine(in, out, length);
2031 }
2032#else
2033 alt->getRGBLine(in, out, length);
2034#endif
2035}
2036
2037void GfxICCBasedColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
2038{
2039#ifdef USE_CMS
2040 if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
2041 unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
2042 lineTransform->doTransform(in, tmp, length);
2043 unsigned char *current = tmp;
2044 for (int i = 0; i < length; ++i) {
2045 *out++ = *current++;
2046 *out++ = *current++;
2047 *out++ = *current++;
2048 *out++ = 255;
2049 }
2050 gfree(tmp);
2051 } else {
2052 alt->getRGBXLine(in, out, length);
2053 }
2054#else
2055 alt->getRGBXLine(in, out, length);
2056#endif
2057}
2058
2059void GfxICCBasedColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
2060{
2061#ifdef USE_CMS
2062 if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
2063 transform->doTransform(in, out, length);
2064 } else if (lineTransform != nullptr && nComps != 4) {
2065 GfxColorComp c, m, y, k;
2066 unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
2067 getRGBLine(in, tmp, length);
2068 unsigned char *p = tmp;
2069 for (int i = 0; i < length; i++) {
2070 c = byteToCol(255 - *p++);
2071 m = byteToCol(255 - *p++);
2072 y = byteToCol(255 - *p++);
2073 k = c;
2074 if (m < k) {
2075 k = m;
2076 }
2077 if (y < k) {
2078 k = y;
2079 }
2080 *out++ = colToByte(c - k);
2081 *out++ = colToByte(m - k);
2082 *out++ = colToByte(y - k);
2083 *out++ = colToByte(k);
2084 }
2085 gfree(tmp);
2086 } else {
2087 alt->getCMYKLine(in, out, length);
2088 }
2089#else
2090 alt->getCMYKLine(in, out, length);
2091#endif
2092}
2093
2094void GfxICCBasedColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
2095{
2096#ifdef USE_CMS
2097 if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
2098 unsigned char *tmp = (unsigned char *)gmallocn(4 * length, sizeof(unsigned char));
2099 transform->doTransform(in, tmp, length);
2100 unsigned char *p = tmp;
2101 for (int i = 0; i < length; i++) {
2102 for (int j = 0; j < 4; j++) {
2103 *out++ = *p++;
2104 }
2105 for (int j = 4; j < SPOT_NCOMPS + 4; j++) {
2106 *out++ = 0;
2107 }
2108 }
2109 gfree(tmp);
2110 } else if (lineTransform != nullptr && nComps != 4) {
2111 GfxColorComp c, m, y, k;
2112 unsigned char *tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
2113 getRGBLine(in, tmp, length);
2114 unsigned char *p = tmp;
2115 for (int i = 0; i < length; i++) {
2116 for (int j = 0; j < SPOT_NCOMPS + 4; j++) {
2117 out[j] = 0;
2118 }
2119 c = byteToCol(255 - *p++);
2120 m = byteToCol(255 - *p++);
2121 y = byteToCol(255 - *p++);
2122 k = c;
2123 if (m < k) {
2124 k = m;
2125 }
2126 if (y < k) {
2127 k = y;
2128 }
2129 out[0] = colToByte(c - k);
2130 out[1] = colToByte(m - k);
2131 out[2] = colToByte(y - k);
2132 out[3] = colToByte(k);
2133 out += (SPOT_NCOMPS + 4);
2134 }
2135 gfree(tmp);
2136 } else {
2137 alt->getDeviceNLine(in, out, length);
2138 }
2139#else
2140 alt->getDeviceNLine(in, out, length);
2141#endif
2142}
2143
2144void GfxICCBasedColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
2145{
2146#ifdef USE_CMS
2147 if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
2148 unsigned char in[gfxColorMaxComps];
2149 unsigned char out[gfxColorMaxComps];
2150
2151 if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
2152 in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
2153 in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
2154 in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
2155 } else {
2156 for (int i = 0; i < nComps; i++) {
2157 in[i] = colToByte(color->c[i]);
2158 }
2159 }
2160 if (nComps <= 4) {
2161 unsigned int key = 0;
2162 for (int j = 0; j < nComps; j++) {
2163 key = (key << 8) + in[j];
2164 }
2165 std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
2166 if (it != cmsCache.end()) {
2167 unsigned int value = it->second;
2168 cmyk->c = byteToCol(value >> 24);
2169 cmyk->m = byteToCol((value >> 16) & 0xff);
2170 cmyk->y = byteToCol((value >> 8) & 0xff);
2171 cmyk->k = byteToCol(value & 0xff);
2172 return;
2173 }
2174 }
2175 transform->doTransform(in, out, 1);
2176 cmyk->c = byteToCol(out[0]);
2177 cmyk->m = byteToCol(out[1]);
2178 cmyk->y = byteToCol(out[2]);
2179 cmyk->k = byteToCol(out[3]);
2180 if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
2181 unsigned int key = 0;
2182 for (int j = 0; j < nComps; j++) {
2183 key = (key << 8) + in[j];
2184 }
2185 unsigned int value = (out[0] << 24) + (out[1] << 16) + (out[2] << 8) + out[3];
2186 cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
2187 }
2188 } else if (nComps != 4 && transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
2189 GfxRGB rgb;
2190 GfxColorComp c, m, y, k;
2191
2192 getRGB(color, &rgb);
2193 c = clip01(gfxColorComp1 - rgb.r);
2194 m = clip01(gfxColorComp1 - rgb.g);
2195 y = clip01(gfxColorComp1 - rgb.b);
2196 k = c;
2197 if (m < k) {
2198 k = m;
2199 }
2200 if (y < k) {
2201 k = y;
2202 }
2203 cmyk->c = c - k;
2204 cmyk->m = m - k;
2205 cmyk->y = y - k;
2206 cmyk->k = k;
2207 } else {
2208 alt->getCMYK(color, cmyk);
2209 }
2210#else
2211 alt->getCMYK(color, cmyk);
2212#endif
2213}
2214
2215bool GfxICCBasedColorSpace::useGetRGBLine() const
2216{
2217#ifdef USE_CMS
2218 return lineTransform != nullptr || alt->useGetRGBLine();
2219#else
2220 return alt->useGetRGBLine();
2221#endif
2222}
2223
2224bool GfxICCBasedColorSpace::useGetCMYKLine() const
2225{
2226#ifdef USE_CMS
2227 return lineTransform != nullptr || alt->useGetCMYKLine();
2228#else
2229 return alt->useGetCMYKLine();
2230#endif
2231}
2232
2233bool GfxICCBasedColorSpace::useGetDeviceNLine() const
2234{
2235#ifdef USE_CMS
2236 return lineTransform != nullptr || alt->useGetDeviceNLine();
2237#else
2238 return alt->useGetDeviceNLine();
2239#endif
2240}
2241
2242void GfxICCBasedColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
2243{
2244 GfxCMYK cmyk;
2245 clearGfxColor(gfxColor: deviceN);
2246 getCMYK(color, cmyk: &cmyk);
2247 deviceN->c[0] = cmyk.c;
2248 deviceN->c[1] = cmyk.m;
2249 deviceN->c[2] = cmyk.y;
2250 deviceN->c[3] = cmyk.k;
2251}
2252
2253void GfxICCBasedColorSpace::getDefaultColor(GfxColor *color) const
2254{
2255 int i;
2256
2257 for (i = 0; i < nComps; ++i) {
2258 if (rangeMin[i] > 0) {
2259 color->c[i] = dblToCol(x: rangeMin[i]);
2260 } else if (rangeMax[i] < 0) {
2261 color->c[i] = dblToCol(x: rangeMax[i]);
2262 } else {
2263 color->c[i] = 0;
2264 }
2265 }
2266}
2267
2268void GfxICCBasedColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
2269{
2270 alt->getDefaultRanges(decodeLow, decodeRange, maxImgPixel);
2271
2272#if 0
2273 // this is nominally correct, but some PDF files don't set the
2274 // correct ranges in the ICCBased dict
2275 int i;
2276
2277 for (i = 0; i < nComps; ++i) {
2278 decodeLow[i] = rangeMin[i];
2279 decodeRange[i] = rangeMax[i] - rangeMin[i];
2280 }
2281#endif
2282}
2283
2284#ifdef USE_CMS
2285char *GfxICCBasedColorSpace::getPostScriptCSA()
2286{
2287# if LCMS_VERSION >= 2070
2288 // The runtime version check of lcms2 is only available from release 2.7 upwards.
2289 // The generation of the CSA code only works reliably for version 2.10 and upwards.
2290 // Cf. the explanation in the corresponding lcms2 merge request [1], and the original mail thread [2].
2291 // [1] https://github.com/mm2/Little-CMS/pull/214
2292 // [2] https://sourceforge.net/p/lcms/mailman/message/33182987/
2293 if (cmsGetEncodedCMMversion() < 2100) {
2294 return nullptr;
2295 }
2296
2297 int size;
2298
2299 if (psCSA) {
2300 return psCSA;
2301 }
2302
2303 if (!profile) {
2304 error(errSyntaxWarning, -1, "profile is nullptr");
2305 return nullptr;
2306 }
2307
2308 void *rawprofile = profile.get();
2309 size = cmsGetPostScriptCSA(cmsGetProfileContextID(rawprofile), rawprofile, getIntent(), 0, nullptr, 0);
2310 if (size == 0) {
2311 error(errSyntaxWarning, -1, "PostScript CSA is nullptr");
2312 return nullptr;
2313 }
2314
2315 psCSA = (char *)gmalloc(size + 1);
2316 cmsGetPostScriptCSA(cmsGetProfileContextID(rawprofile), rawprofile, getIntent(), 0, psCSA, size);
2317 psCSA[size] = 0;
2318
2319 // TODO REMOVE-ME-IN-THE-FUTURE
2320 // until we can depend on https://github.com/mm2/Little-CMS/issues/223 being fixed
2321 // lcms returns ps code with , instead of . for some locales. The lcms author says
2322 // that there's no room for any , in the rest of the ps code, so replacing all the , with .
2323 // is an "acceptable" workaround
2324 for (int i = 0; i < size; ++i) {
2325 if (psCSA[i] == ',') {
2326 psCSA[i] = '.';
2327 }
2328 }
2329
2330 return psCSA;
2331# else
2332 return nullptr;
2333# endif
2334}
2335#endif
2336
2337//------------------------------------------------------------------------
2338// GfxIndexedColorSpace
2339//------------------------------------------------------------------------
2340
2341GfxIndexedColorSpace::GfxIndexedColorSpace(GfxColorSpace *baseA, int indexHighA)
2342{
2343 base = baseA;
2344 indexHigh = indexHighA;
2345 lookup = (unsigned char *)gmallocn(count: (indexHigh + 1) * base->getNComps(), size: sizeof(unsigned char));
2346 overprintMask = base->getOverprintMask();
2347}
2348
2349GfxIndexedColorSpace::~GfxIndexedColorSpace()
2350{
2351 delete base;
2352 gfree(p: lookup);
2353}
2354
2355GfxColorSpace *GfxIndexedColorSpace::copy() const
2356{
2357 GfxIndexedColorSpace *cs;
2358
2359 cs = new GfxIndexedColorSpace(base->copy(), indexHigh);
2360 memcpy(dest: cs->lookup, src: lookup, n: (indexHigh + 1) * base->getNComps() * sizeof(unsigned char));
2361 return cs;
2362}
2363
2364GfxColorSpace *GfxIndexedColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion)
2365{
2366 GfxColorSpace *baseA;
2367 int indexHighA;
2368 Object obj1;
2369 const char *s;
2370 int i, j;
2371
2372 if (arr->getLength() != 4) {
2373 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space");
2374 return nullptr;
2375 }
2376 obj1 = arr->get(i: 1);
2377 if (!(baseA = GfxColorSpace::parse(res, csObj: &obj1, out, state, recursion: recursion + 1))) {
2378 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (base color space)");
2379 return nullptr;
2380 }
2381 obj1 = arr->get(i: 2);
2382 if (!obj1.isInt()) {
2383 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (hival)");
2384 delete baseA;
2385 return nullptr;
2386 }
2387 indexHighA = obj1.getInt();
2388 if (indexHighA < 0 || indexHighA > 255) {
2389 // the PDF spec requires indexHigh to be in [0,255] -- allowing
2390 // values larger than 255 creates a security hole: if nComps *
2391 // indexHigh is greater than 2^31, the loop below may overwrite
2392 // past the end of the array
2393 int previousValue = indexHighA;
2394 if (indexHighA < 0) {
2395 indexHighA = 0;
2396 } else {
2397 indexHighA = 255;
2398 }
2399 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (invalid indexHigh value, was {0:d} using {1:d} to try to recover)", previousValue, indexHighA);
2400 }
2401 GfxIndexedColorSpace *cs = new GfxIndexedColorSpace(baseA, indexHighA);
2402 obj1 = arr->get(i: 3);
2403 const int n = baseA->getNComps();
2404 if (obj1.isStream()) {
2405 obj1.streamReset();
2406 for (i = 0; i <= indexHighA; ++i) {
2407 const int readChars = obj1.streamGetChars(nChars: n, buffer: &cs->lookup[i * n]);
2408 for (j = readChars; j < n; ++j) {
2409 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (lookup table stream too short) padding with zeroes");
2410 cs->lookup[i * n + j] = 0;
2411 }
2412 }
2413 obj1.streamClose();
2414 } else if (obj1.isString()) {
2415 if (obj1.getString()->getLength() < (indexHighA + 1) * n) {
2416 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (lookup table string too short)");
2417 goto err3;
2418 }
2419 s = obj1.getString()->c_str();
2420 for (i = 0; i <= indexHighA; ++i) {
2421 for (j = 0; j < n; ++j) {
2422 cs->lookup[i * n + j] = (unsigned char)*s++;
2423 }
2424 }
2425 } else {
2426 error(category: errSyntaxWarning, pos: -1, msg: "Bad Indexed color space (lookup table)");
2427 goto err3;
2428 }
2429 return cs;
2430
2431err3:
2432 delete cs;
2433 return nullptr;
2434}
2435
2436GfxColor *GfxIndexedColorSpace::mapColorToBase(const GfxColor *color, GfxColor *baseColor) const
2437{
2438 unsigned char *p;
2439 double low[gfxColorMaxComps], range[gfxColorMaxComps];
2440 int n, i;
2441
2442 n = base->getNComps();
2443 base->getDefaultRanges(decodeLow: low, decodeRange: range, maxImgPixel: indexHigh);
2444 const int idx = (int)(colToDbl(x: color->c[0]) + 0.5) * n;
2445 if (likely((idx + n - 1 < (indexHigh + 1) * base->getNComps()) && idx >= 0)) {
2446 p = &lookup[idx];
2447 for (i = 0; i < n; ++i) {
2448 baseColor->c[i] = dblToCol(x: low[i] + (p[i] / 255.0) * range[i]);
2449 }
2450 } else {
2451 for (i = 0; i < n; ++i) {
2452 baseColor->c[i] = 0;
2453 }
2454 }
2455 return baseColor;
2456}
2457
2458void GfxIndexedColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
2459{
2460 GfxColor color2;
2461
2462 base->getGray(color: mapColorToBase(color, baseColor: &color2), gray);
2463}
2464
2465void GfxIndexedColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
2466{
2467 GfxColor color2;
2468
2469 base->getRGB(color: mapColorToBase(color, baseColor: &color2), rgb);
2470}
2471
2472void GfxIndexedColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
2473{
2474 unsigned char *line;
2475 int i, j, n;
2476
2477 n = base->getNComps();
2478 line = (unsigned char *)gmallocn(count: length, size: n);
2479 for (i = 0; i < length; i++) {
2480 for (j = 0; j < n; j++) {
2481 line[i * n + j] = lookup[in[i] * n + j];
2482 }
2483 }
2484
2485 base->getRGBLine(line, out, length);
2486
2487 gfree(p: line);
2488}
2489
2490void GfxIndexedColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
2491{
2492 unsigned char *line;
2493 int i, j, n;
2494
2495 n = base->getNComps();
2496 line = (unsigned char *)gmallocn(count: length, size: n);
2497 for (i = 0; i < length; i++) {
2498 for (j = 0; j < n; j++) {
2499 line[i * n + j] = lookup[in[i] * n + j];
2500 }
2501 }
2502
2503 base->getRGBLine(line, out, length);
2504
2505 gfree(p: line);
2506}
2507
2508void GfxIndexedColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
2509{
2510 unsigned char *line;
2511 int i, j, n;
2512
2513 n = base->getNComps();
2514 line = (unsigned char *)gmallocn(count: length, size: n);
2515 for (i = 0; i < length; i++) {
2516 for (j = 0; j < n; j++) {
2517 line[i * n + j] = lookup[in[i] * n + j];
2518 }
2519 }
2520
2521 base->getRGBXLine(line, out, length);
2522
2523 gfree(p: line);
2524}
2525
2526void GfxIndexedColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
2527{
2528 unsigned char *line;
2529 int i, j, n;
2530
2531 n = base->getNComps();
2532 line = (unsigned char *)gmallocn(count: length, size: n);
2533 for (i = 0; i < length; i++) {
2534 for (j = 0; j < n; j++) {
2535 line[i * n + j] = lookup[in[i] * n + j];
2536 }
2537 }
2538
2539 base->getCMYKLine(line, out, length);
2540
2541 gfree(p: line);
2542}
2543
2544void GfxIndexedColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
2545{
2546 unsigned char *line;
2547 int i, j, n;
2548
2549 n = base->getNComps();
2550 line = (unsigned char *)gmallocn(count: length, size: n);
2551 for (i = 0; i < length; i++) {
2552 for (j = 0; j < n; j++) {
2553 line[i * n + j] = lookup[in[i] * n + j];
2554 }
2555 }
2556
2557 base->getDeviceNLine(line, out, length);
2558
2559 gfree(p: line);
2560}
2561
2562void GfxIndexedColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
2563{
2564 GfxColor color2;
2565
2566 base->getCMYK(color: mapColorToBase(color, baseColor: &color2), cmyk);
2567}
2568
2569void GfxIndexedColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
2570{
2571 GfxColor color2;
2572
2573 base->getDeviceN(color: mapColorToBase(color, baseColor: &color2), deviceN);
2574}
2575
2576void GfxIndexedColorSpace::getDefaultColor(GfxColor *color) const
2577{
2578 color->c[0] = 0;
2579}
2580
2581void GfxIndexedColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const
2582{
2583 decodeLow[0] = 0;
2584 decodeRange[0] = maxImgPixel;
2585}
2586
2587//------------------------------------------------------------------------
2588// GfxSeparationColorSpace
2589//------------------------------------------------------------------------
2590
2591GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA, GfxColorSpace *altA, Function *funcA)
2592{
2593 name = nameA;
2594 alt = altA;
2595 func = funcA;
2596 nonMarking = !name->cmp(sA: "None");
2597 if (!name->cmp(sA: "Cyan")) {
2598 overprintMask = 0x01;
2599 } else if (!name->cmp(sA: "Magenta")) {
2600 overprintMask = 0x02;
2601 } else if (!name->cmp(sA: "Yellow")) {
2602 overprintMask = 0x04;
2603 } else if (!name->cmp(sA: "Black")) {
2604 overprintMask = 0x08;
2605 } else if (!name->cmp(sA: "All")) {
2606 overprintMask = 0xffffffff;
2607 }
2608}
2609
2610GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA, GfxColorSpace *altA, Function *funcA, bool nonMarkingA, unsigned int overprintMaskA, int *mappingA)
2611{
2612 name = nameA;
2613 alt = altA;
2614 func = funcA;
2615 nonMarking = nonMarkingA;
2616 overprintMask = overprintMaskA;
2617 mapping = mappingA;
2618}
2619
2620GfxSeparationColorSpace::~GfxSeparationColorSpace()
2621{
2622 delete name;
2623 delete alt;
2624 delete func;
2625 if (mapping != nullptr) {
2626 gfree(p: mapping);
2627 }
2628}
2629
2630GfxColorSpace *GfxSeparationColorSpace::copy() const
2631{
2632 int *mappingA = nullptr;
2633 if (mapping != nullptr) {
2634 mappingA = (int *)gmalloc(size: sizeof(int));
2635 *mappingA = *mapping;
2636 }
2637 return new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy(), nonMarking, overprintMask, mappingA);
2638}
2639
2640//~ handle the 'All' and 'None' colorants
2641GfxColorSpace *GfxSeparationColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion)
2642{
2643 GooString *nameA;
2644 GfxColorSpace *altA;
2645 Function *funcA;
2646 Object obj1;
2647
2648 if (arr->getLength() != 4) {
2649 error(category: errSyntaxWarning, pos: -1, msg: "Bad Separation color space");
2650 goto err1;
2651 }
2652 obj1 = arr->get(i: 1);
2653 if (!obj1.isName()) {
2654 error(category: errSyntaxWarning, pos: -1, msg: "Bad Separation color space (name)");
2655 goto err1;
2656 }
2657 nameA = new GooString(obj1.getName());
2658 obj1 = arr->get(i: 2);
2659 if (!(altA = GfxColorSpace::parse(res, csObj: &obj1, out, state, recursion: recursion + 1))) {
2660 error(category: errSyntaxWarning, pos: -1, msg: "Bad Separation color space (alternate color space)");
2661 goto err3;
2662 }
2663 obj1 = arr->get(i: 3);
2664 if (!(funcA = Function::parse(funcObj: &obj1))) {
2665 goto err4;
2666 }
2667 if (funcA->getInputSize() != 1) {
2668 error(category: errSyntaxWarning, pos: -1, msg: "Bad SeparationColorSpace function");
2669 goto err5;
2670 }
2671 if (altA->getNComps() <= funcA->getOutputSize()) {
2672 return new GfxSeparationColorSpace(nameA, altA, funcA);
2673 }
2674
2675err5:
2676 delete funcA;
2677err4:
2678 delete altA;
2679err3:
2680 delete nameA;
2681err1:
2682 return nullptr;
2683}
2684
2685void GfxSeparationColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
2686{
2687 double x;
2688 double c[gfxColorMaxComps];
2689 GfxColor color2;
2690 int i;
2691
2692 if (alt->getMode() == csDeviceGray && name->cmp(sA: "Black") == 0) {
2693 *gray = clip01(gfxColorComp1 - color->c[0]);
2694 } else {
2695 x = colToDbl(x: color->c[0]);
2696 func->transform(in: &x, out: c);
2697 for (i = 0; i < alt->getNComps(); ++i) {
2698 color2.c[i] = dblToCol(x: c[i]);
2699 }
2700 alt->getGray(color: &color2, gray);
2701 }
2702}
2703
2704void GfxSeparationColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
2705{
2706 double x;
2707 double c[gfxColorMaxComps];
2708 GfxColor color2;
2709 int i;
2710
2711 if (alt->getMode() == csDeviceGray && name->cmp(sA: "Black") == 0) {
2712 rgb->r = clip01(gfxColorComp1 - color->c[0]);
2713 rgb->g = clip01(gfxColorComp1 - color->c[0]);
2714 rgb->b = clip01(gfxColorComp1 - color->c[0]);
2715 } else {
2716 x = colToDbl(x: color->c[0]);
2717 func->transform(in: &x, out: c);
2718 const int altNComps = alt->getNComps();
2719 for (i = 0; i < altNComps; ++i) {
2720 color2.c[i] = dblToCol(x: c[i]);
2721 }
2722 alt->getRGB(color: &color2, rgb);
2723 }
2724}
2725
2726void GfxSeparationColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
2727{
2728 double x;
2729 double c[gfxColorMaxComps];
2730 GfxColor color2;
2731 int i;
2732
2733 if (name->cmp(sA: "Black") == 0) {
2734 cmyk->c = 0;
2735 cmyk->m = 0;
2736 cmyk->y = 0;
2737 cmyk->k = color->c[0];
2738 } else if (name->cmp(sA: "Cyan") == 0) {
2739 cmyk->c = color->c[0];
2740 cmyk->m = 0;
2741 cmyk->y = 0;
2742 cmyk->k = 0;
2743 } else if (name->cmp(sA: "Magenta") == 0) {
2744 cmyk->c = 0;
2745 cmyk->m = color->c[0];
2746 cmyk->y = 0;
2747 cmyk->k = 0;
2748 } else if (name->cmp(sA: "Yellow") == 0) {
2749 cmyk->c = 0;
2750 cmyk->m = 0;
2751 cmyk->y = color->c[0];
2752 cmyk->k = 0;
2753 } else {
2754 x = colToDbl(x: color->c[0]);
2755 func->transform(in: &x, out: c);
2756 for (i = 0; i < alt->getNComps(); ++i) {
2757 color2.c[i] = dblToCol(x: c[i]);
2758 }
2759 alt->getCMYK(color: &color2, cmyk);
2760 }
2761}
2762
2763void GfxSeparationColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
2764{
2765 clearGfxColor(gfxColor: deviceN);
2766 if (mapping == nullptr || mapping[0] == -1) {
2767 GfxCMYK cmyk;
2768
2769 getCMYK(color, cmyk: &cmyk);
2770 deviceN->c[0] = cmyk.c;
2771 deviceN->c[1] = cmyk.m;
2772 deviceN->c[2] = cmyk.y;
2773 deviceN->c[3] = cmyk.k;
2774 } else {
2775 deviceN->c[mapping[0]] = color->c[0];
2776 }
2777}
2778
2779void GfxSeparationColorSpace::getDefaultColor(GfxColor *color) const
2780{
2781 color->c[0] = gfxColorComp1;
2782}
2783
2784void GfxSeparationColorSpace::createMapping(std::vector<GfxSeparationColorSpace *> *separationList, int maxSepComps)
2785{
2786 if (nonMarking) {
2787 return;
2788 }
2789 mapping = (int *)gmalloc(size: sizeof(int));
2790 switch (overprintMask) {
2791 case 0x01:
2792 *mapping = 0;
2793 break;
2794 case 0x02:
2795 *mapping = 1;
2796 break;
2797 case 0x04:
2798 *mapping = 2;
2799 break;
2800 case 0x08:
2801 *mapping = 3;
2802 break;
2803 default:
2804 unsigned int newOverprintMask = 0x10;
2805 for (std::size_t i = 0; i < separationList->size(); i++) {
2806 GfxSeparationColorSpace *sepCS = (*separationList)[i];
2807 if (!sepCS->getName()->cmp(str: name)) {
2808 if (sepCS->getFunc()->hasDifferentResultSet(func)) {
2809 error(category: errSyntaxWarning, pos: -1, msg: "Different functions found for '{0:t}', convert immediately", name);
2810 gfree(p: mapping);
2811 mapping = nullptr;
2812 return;
2813 }
2814 *mapping = i + 4;
2815 overprintMask = newOverprintMask;
2816 return;
2817 }
2818 newOverprintMask <<= 1;
2819 }
2820 if ((int)separationList->size() == maxSepComps) {
2821 error(category: errSyntaxWarning, pos: -1, msg: "Too many ({0:d}) spots, convert '{1:t}' immediately", maxSepComps, name);
2822 gfree(p: mapping);
2823 mapping = nullptr;
2824 return;
2825 }
2826 *mapping = separationList->size() + 4;
2827 separationList->push_back(x: (GfxSeparationColorSpace *)copy());
2828 overprintMask = newOverprintMask;
2829 break;
2830 }
2831}
2832
2833//------------------------------------------------------------------------
2834// GfxDeviceNColorSpace
2835//------------------------------------------------------------------------
2836
2837GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA, std::vector<std::string> &&namesA, GfxColorSpace *altA, Function *funcA, std::vector<GfxSeparationColorSpace *> *sepsCSA) : nComps(nCompsA), names(std::move(namesA))
2838{
2839 alt = altA;
2840 func = funcA;
2841 sepsCS = sepsCSA;
2842 nonMarking = true;
2843 overprintMask = 0;
2844 mapping = nullptr;
2845 for (int i = 0; i < nComps; ++i) {
2846 if (names[i] != "None") {
2847 nonMarking = false;
2848 }
2849 if (names[i] == "Cyan") {
2850 overprintMask |= 0x01;
2851 } else if (names[i] == "Magenta") {
2852 overprintMask |= 0x02;
2853 } else if (names[i] == "Yellow") {
2854 overprintMask |= 0x04;
2855 } else if (names[i] == "Black") {
2856 overprintMask |= 0x08;
2857 } else if (names[i] == "All") {
2858 overprintMask = 0xffffffff;
2859 } else if (names[i] != "None") {
2860 overprintMask = 0x0f;
2861 }
2862 }
2863}
2864
2865GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA, const std::vector<std::string> &namesA, GfxColorSpace *altA, Function *funcA, std::vector<GfxSeparationColorSpace *> *sepsCSA, int *mappingA, bool nonMarkingA,
2866 unsigned int overprintMaskA)
2867 : nComps(nCompsA), names(namesA)
2868{
2869 alt = altA;
2870 func = funcA;
2871 sepsCS = sepsCSA;
2872 mapping = mappingA;
2873 nonMarking = nonMarkingA;
2874 overprintMask = overprintMaskA;
2875}
2876
2877GfxDeviceNColorSpace::~GfxDeviceNColorSpace()
2878{
2879 delete alt;
2880 delete func;
2881 for (auto entry : *sepsCS) {
2882 delete entry;
2883 }
2884 delete sepsCS;
2885 if (mapping != nullptr) {
2886 gfree(p: mapping);
2887 }
2888}
2889
2890GfxColorSpace *GfxDeviceNColorSpace::copy() const
2891{
2892 int *mappingA = nullptr;
2893
2894 auto sepsCSA = new std::vector<GfxSeparationColorSpace *>();
2895 sepsCSA->reserve(n: sepsCS->size());
2896 for (const GfxSeparationColorSpace *scs : *sepsCS) {
2897 if (likely(scs != nullptr)) {
2898 sepsCSA->push_back(x: (GfxSeparationColorSpace *)scs->copy());
2899 }
2900 }
2901 if (mapping != nullptr) {
2902 mappingA = (int *)gmalloc(size: sizeof(int) * nComps);
2903 for (int i = 0; i < nComps; i++) {
2904 mappingA[i] = mapping[i];
2905 }
2906 }
2907 return new GfxDeviceNColorSpace(nComps, names, alt->copy(), func->copy(), sepsCSA, mappingA, nonMarking, overprintMask);
2908}
2909
2910//~ handle the 'None' colorant
2911GfxColorSpace *GfxDeviceNColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion)
2912{
2913 int nCompsA;
2914 std::vector<std::string> namesA;
2915 GfxColorSpace *altA;
2916 Function *funcA;
2917 Object obj1;
2918 auto separationList = new std::vector<GfxSeparationColorSpace *>();
2919
2920 if (arr->getLength() != 4 && arr->getLength() != 5) {
2921 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space");
2922 goto err1;
2923 }
2924 obj1 = arr->get(i: 1);
2925 if (!obj1.isArray()) {
2926 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space (names)");
2927 goto err1;
2928 }
2929 nCompsA = obj1.arrayGetLength();
2930 if (nCompsA > gfxColorMaxComps) {
2931 error(category: errSyntaxWarning, pos: -1, msg: "DeviceN color space with too many ({0:d} > {1:d}) components", nCompsA, gfxColorMaxComps);
2932 nCompsA = gfxColorMaxComps;
2933 }
2934 for (int i = 0; i < nCompsA; ++i) {
2935 Object obj2 = obj1.arrayGet(i);
2936 if (!obj2.isName()) {
2937 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space (names)");
2938 nCompsA = i;
2939 goto err1;
2940 }
2941 namesA.emplace_back(args: obj2.getName());
2942 }
2943 obj1 = arr->get(i: 2);
2944 if (!(altA = GfxColorSpace::parse(res, csObj: &obj1, out, state, recursion: recursion + 1))) {
2945 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space (alternate color space)");
2946 goto err1;
2947 }
2948 obj1 = arr->get(i: 3);
2949 if (!(funcA = Function::parse(funcObj: &obj1))) {
2950 goto err4;
2951 }
2952 if (arr->getLength() == 5) {
2953 obj1 = arr->get(i: 4);
2954 if (!obj1.isDict()) {
2955 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space (attributes)");
2956 goto err5;
2957 }
2958 Dict *attribs = obj1.getDict();
2959 Object obj2 = attribs->lookup(key: "Colorants");
2960 if (obj2.isDict()) {
2961 Dict *colorants = obj2.getDict();
2962 for (int i = 0; i < colorants->getLength(); i++) {
2963 Object obj3 = colorants->getVal(i);
2964 if (obj3.isArray()) {
2965 GfxSeparationColorSpace *cs = (GfxSeparationColorSpace *)GfxSeparationColorSpace::parse(res, arr: obj3.getArray(), out, state, recursion);
2966 if (cs) {
2967 separationList->push_back(x: cs);
2968 }
2969 } else {
2970 error(category: errSyntaxWarning, pos: -1, msg: "Bad DeviceN color space (colorant value entry is not an Array)");
2971 goto err5;
2972 }
2973 }
2974 }
2975 }
2976
2977 if (likely(nCompsA >= funcA->getInputSize() && altA->getNComps() <= funcA->getOutputSize())) {
2978 return new GfxDeviceNColorSpace(nCompsA, std::move(namesA), altA, funcA, separationList);
2979 }
2980
2981err5:
2982 delete funcA;
2983err4:
2984 delete altA;
2985err1:
2986 delete separationList;
2987 return nullptr;
2988}
2989
2990void GfxDeviceNColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
2991{
2992 double x[gfxColorMaxComps], c[gfxColorMaxComps];
2993 GfxColor color2;
2994 int i;
2995
2996 for (i = 0; i < nComps; ++i) {
2997 x[i] = colToDbl(x: color->c[i]);
2998 }
2999 func->transform(in: x, out: c);
3000 for (i = 0; i < alt->getNComps(); ++i) {
3001 color2.c[i] = dblToCol(x: c[i]);
3002 }
3003 alt->getGray(color: &color2, gray);
3004}
3005
3006void GfxDeviceNColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
3007{
3008 double x[gfxColorMaxComps], c[gfxColorMaxComps];
3009 GfxColor color2;
3010 int i;
3011
3012 for (i = 0; i < nComps; ++i) {
3013 x[i] = colToDbl(x: color->c[i]);
3014 }
3015 func->transform(in: x, out: c);
3016 for (i = 0; i < alt->getNComps(); ++i) {
3017 color2.c[i] = dblToCol(x: c[i]);
3018 }
3019 alt->getRGB(color: &color2, rgb);
3020}
3021
3022void GfxDeviceNColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
3023{
3024 double x[gfxColorMaxComps], c[gfxColorMaxComps];
3025 GfxColor color2;
3026 int i;
3027
3028 for (i = 0; i < nComps; ++i) {
3029 x[i] = colToDbl(x: color->c[i]);
3030 }
3031 func->transform(in: x, out: c);
3032 for (i = 0; i < alt->getNComps(); ++i) {
3033 color2.c[i] = dblToCol(x: c[i]);
3034 }
3035 alt->getCMYK(color: &color2, cmyk);
3036}
3037
3038void GfxDeviceNColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
3039{
3040 clearGfxColor(gfxColor: deviceN);
3041 if (mapping == nullptr) {
3042 GfxCMYK cmyk;
3043
3044 getCMYK(color, cmyk: &cmyk);
3045 deviceN->c[0] = cmyk.c;
3046 deviceN->c[1] = cmyk.m;
3047 deviceN->c[2] = cmyk.y;
3048 deviceN->c[3] = cmyk.k;
3049 } else {
3050 for (int j = 0; j < nComps; j++) {
3051 if (mapping[j] != -1) {
3052 deviceN->c[mapping[j]] = color->c[j];
3053 }
3054 }
3055 }
3056}
3057
3058void GfxDeviceNColorSpace::getDefaultColor(GfxColor *color) const
3059{
3060 int i;
3061
3062 for (i = 0; i < nComps; ++i) {
3063 color->c[i] = gfxColorComp1;
3064 }
3065}
3066
3067void GfxDeviceNColorSpace::createMapping(std::vector<GfxSeparationColorSpace *> *separationList, int maxSepComps)
3068{
3069 if (nonMarking) { // None
3070 return;
3071 }
3072 mapping = (int *)gmalloc(size: sizeof(int) * nComps);
3073 unsigned int newOverprintMask = 0;
3074 for (int i = 0; i < nComps; i++) {
3075 if (names[i] == "None") {
3076 mapping[i] = -1;
3077 } else if (names[i] == "Cyan") {
3078 newOverprintMask |= 0x01;
3079 mapping[i] = 0;
3080 } else if (names[i] == "Magenta") {
3081 newOverprintMask |= 0x02;
3082 mapping[i] = 1;
3083 } else if (names[i] == "Yellow") {
3084 newOverprintMask |= 0x04;
3085 mapping[i] = 2;
3086 } else if (names[i] == "Black") {
3087 newOverprintMask |= 0x08;
3088 mapping[i] = 3;
3089 } else {
3090 unsigned int startOverprintMask = 0x10;
3091 bool found = false;
3092 const Function *sepFunc = nullptr;
3093 if (nComps == 1) {
3094 sepFunc = func;
3095 } else {
3096 for (const GfxSeparationColorSpace *sepCS : *sepsCS) {
3097 if (!sepCS->getName()->cmp(str: names[i])) {
3098 sepFunc = sepCS->getFunc();
3099 break;
3100 }
3101 }
3102 }
3103 for (std::size_t j = 0; j < separationList->size(); j++) {
3104 GfxSeparationColorSpace *sepCS = (*separationList)[j];
3105 if (!sepCS->getName()->cmp(str: names[i])) {
3106 if (sepFunc != nullptr && sepCS->getFunc()->hasDifferentResultSet(func: sepFunc)) {
3107 error(category: errSyntaxWarning, pos: -1, msg: "Different functions found for '{0:s}', convert immediately", names[i].c_str());
3108 gfree(p: mapping);
3109 mapping = nullptr;
3110 overprintMask = 0xffffffff;
3111 return;
3112 }
3113 mapping[i] = j + 4;
3114 newOverprintMask |= startOverprintMask;
3115 found = true;
3116 break;
3117 }
3118 startOverprintMask <<= 1;
3119 }
3120 if (!found) {
3121 if ((int)separationList->size() == maxSepComps) {
3122 error(category: errSyntaxWarning, pos: -1, msg: "Too many ({0:d}) spots, convert '{1:s}' immediately", maxSepComps, names[i].c_str());
3123 gfree(p: mapping);
3124 mapping = nullptr;
3125 overprintMask = 0xffffffff;
3126 return;
3127 }
3128 mapping[i] = separationList->size() + 4;
3129 newOverprintMask |= startOverprintMask;
3130 if (nComps == 1) {
3131 separationList->push_back(x: new GfxSeparationColorSpace(new GooString(names[i]), alt->copy(), func->copy()));
3132 } else {
3133 for (const GfxSeparationColorSpace *sepCS : *sepsCS) {
3134 if (!sepCS->getName()->cmp(str: names[i])) {
3135 found = true;
3136 separationList->push_back(x: (GfxSeparationColorSpace *)sepCS->copy());
3137 break;
3138 }
3139 }
3140 if (!found) {
3141 error(category: errSyntaxWarning, pos: -1, msg: "DeviceN has no suitable colorant");
3142 gfree(p: mapping);
3143 mapping = nullptr;
3144 overprintMask = 0xffffffff;
3145 return;
3146 }
3147 }
3148 }
3149 }
3150 }
3151 overprintMask = newOverprintMask;
3152}
3153
3154//------------------------------------------------------------------------
3155// GfxPatternColorSpace
3156//------------------------------------------------------------------------
3157
3158GfxPatternColorSpace::GfxPatternColorSpace(GfxColorSpace *underA)
3159{
3160 under = underA;
3161}
3162
3163GfxPatternColorSpace::~GfxPatternColorSpace()
3164{
3165 if (under) {
3166 delete under;
3167 }
3168}
3169
3170GfxColorSpace *GfxPatternColorSpace::copy() const
3171{
3172 return new GfxPatternColorSpace(under ? under->copy() : nullptr);
3173}
3174
3175GfxColorSpace *GfxPatternColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion)
3176{
3177 GfxPatternColorSpace *cs;
3178 GfxColorSpace *underA;
3179 Object obj1;
3180
3181 if (arr->getLength() != 1 && arr->getLength() != 2) {
3182 error(category: errSyntaxWarning, pos: -1, msg: "Bad Pattern color space");
3183 return nullptr;
3184 }
3185 underA = nullptr;
3186 if (arr->getLength() == 2) {
3187 obj1 = arr->get(i: 1);
3188 if (!(underA = GfxColorSpace::parse(res, csObj: &obj1, out, state, recursion: recursion + 1))) {
3189 error(category: errSyntaxWarning, pos: -1, msg: "Bad Pattern color space (underlying color space)");
3190 return nullptr;
3191 }
3192 }
3193 cs = new GfxPatternColorSpace(underA);
3194 return cs;
3195}
3196
3197void GfxPatternColorSpace::getGray(const GfxColor *color, GfxGray *gray) const
3198{
3199 *gray = 0;
3200}
3201
3202void GfxPatternColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const
3203{
3204 rgb->r = rgb->g = rgb->b = 0;
3205}
3206
3207void GfxPatternColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const
3208{
3209 cmyk->c = cmyk->m = cmyk->y = 0;
3210 cmyk->k = 1;
3211}
3212
3213void GfxPatternColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const
3214{
3215 clearGfxColor(gfxColor: deviceN);
3216 deviceN->c[3] = 1;
3217}
3218
3219void GfxPatternColorSpace::getDefaultColor(GfxColor *color) const
3220{
3221 color->c[0] = 0;
3222}
3223
3224//------------------------------------------------------------------------
3225// Pattern
3226//------------------------------------------------------------------------
3227
3228GfxPattern::GfxPattern(int typeA, int patternRefNumA) : type(typeA), patternRefNum(patternRefNumA) { }
3229
3230GfxPattern::~GfxPattern() { }
3231
3232GfxPattern *GfxPattern::parse(GfxResources *res, Object *obj, OutputDev *out, GfxState *state, int patternRefNum)
3233{
3234 GfxPattern *pattern;
3235 Object obj1;
3236
3237 if (obj->isDict()) {
3238 obj1 = obj->dictLookup(key: "PatternType");
3239 } else if (obj->isStream()) {
3240 obj1 = obj->streamGetDict()->lookup(key: "PatternType");
3241 } else {
3242 return nullptr;
3243 }
3244 pattern = nullptr;
3245 if (obj1.isInt() && obj1.getInt() == 1) {
3246 pattern = GfxTilingPattern::parse(patObj: obj, patternRefNum);
3247 } else if (obj1.isInt() && obj1.getInt() == 2) {
3248 pattern = GfxShadingPattern::parse(res, patObj: obj, out, state, patternRefNum);
3249 }
3250 return pattern;
3251}
3252
3253//------------------------------------------------------------------------
3254// GfxTilingPattern
3255//------------------------------------------------------------------------
3256
3257GfxTilingPattern *GfxTilingPattern::parse(Object *patObj, int patternRefNum)
3258{
3259 Dict *dict;
3260 int paintTypeA, tilingTypeA;
3261 double bboxA[4], matrixA[6];
3262 double xStepA, yStepA;
3263 Object resDictA;
3264 Object obj1;
3265 int i;
3266
3267 if (!patObj->isStream()) {
3268 return nullptr;
3269 }
3270 dict = patObj->streamGetDict();
3271
3272 obj1 = dict->lookup(key: "PaintType");
3273 if (obj1.isInt()) {
3274 paintTypeA = obj1.getInt();
3275 } else {
3276 paintTypeA = 1;
3277 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing PaintType in pattern");
3278 }
3279 obj1 = dict->lookup(key: "TilingType");
3280 if (obj1.isInt()) {
3281 tilingTypeA = obj1.getInt();
3282 } else {
3283 tilingTypeA = 1;
3284 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing TilingType in pattern");
3285 }
3286 bboxA[0] = bboxA[1] = 0;
3287 bboxA[2] = bboxA[3] = 1;
3288 obj1 = dict->lookup(key: "BBox");
3289 if (obj1.isArray() && obj1.arrayGetLength() == 4) {
3290 for (i = 0; i < 4; ++i) {
3291 Object obj2 = obj1.arrayGet(i);
3292 if (obj2.isNum()) {
3293 bboxA[i] = obj2.getNum();
3294 }
3295 }
3296 } else {
3297 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing BBox in pattern");
3298 }
3299 obj1 = dict->lookup(key: "XStep");
3300 if (obj1.isNum()) {
3301 xStepA = obj1.getNum();
3302 } else {
3303 xStepA = 1;
3304 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing XStep in pattern");
3305 }
3306 obj1 = dict->lookup(key: "YStep");
3307 if (obj1.isNum()) {
3308 yStepA = obj1.getNum();
3309 } else {
3310 yStepA = 1;
3311 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing YStep in pattern");
3312 }
3313 resDictA = dict->lookup(key: "Resources");
3314 if (!resDictA.isDict()) {
3315 error(category: errSyntaxWarning, pos: -1, msg: "Invalid or missing Resources in pattern");
3316 }
3317 matrixA[0] = 1;
3318 matrixA[1] = 0;
3319 matrixA[2] = 0;
3320 matrixA[3] = 1;
3321 matrixA[4] = 0;
3322 matrixA[5] = 0;
3323 obj1 = dict->lookup(key: "Matrix");
3324 if (obj1.isArray() && obj1.arrayGetLength() == 6) {
3325 for (i = 0; i < 6; ++i) {
3326 Object obj2 = obj1.arrayGet(i);
3327 if (obj2.isNum()) {
3328 matrixA[i] = obj2.getNum();
3329 }
3330 }
3331 }
3332
3333 return new GfxTilingPattern(paintTypeA, tilingTypeA, bboxA, xStepA, yStepA, &resDictA, matrixA, patObj, patternRefNum);
3334}
3335
3336GfxTilingPattern::GfxTilingPattern(int paintTypeA, int tilingTypeA, const double *bboxA, double xStepA, double yStepA, const Object *resDictA, const double *matrixA, const Object *contentStreamA, int patternRefNumA)
3337 : GfxPattern(1, patternRefNumA)
3338{
3339 int i;
3340
3341 paintType = paintTypeA;
3342 tilingType = tilingTypeA;
3343 for (i = 0; i < 4; ++i) {
3344 bbox[i] = bboxA[i];
3345 }
3346 xStep = xStepA;
3347 yStep = yStepA;
3348 resDict = resDictA->copy();
3349 for (i = 0; i < 6; ++i) {
3350 matrix[i] = matrixA[i];
3351 }
3352 contentStream = contentStreamA->copy();
3353}
3354
3355GfxTilingPattern::~GfxTilingPattern() { }
3356
3357GfxPattern *GfxTilingPattern::copy() const
3358{
3359 return new GfxTilingPattern(paintType, tilingType, bbox, xStep, yStep, &resDict, matrix, &contentStream, getPatternRefNum());
3360}
3361
3362//------------------------------------------------------------------------
3363// GfxShadingPattern
3364//------------------------------------------------------------------------
3365
3366GfxShadingPattern *GfxShadingPattern::parse(GfxResources *res, Object *patObj, OutputDev *out, GfxState *state, int patternRefNum)
3367{
3368 Dict *dict;
3369 GfxShading *shadingA;
3370 double matrixA[6];
3371 Object obj1;
3372 int i;
3373
3374 if (!patObj->isDict()) {
3375 return nullptr;
3376 }
3377 dict = patObj->getDict();
3378
3379 obj1 = dict->lookup(key: "Shading");
3380 shadingA = GfxShading::parse(res, obj: &obj1, out, state);
3381 if (!shadingA) {
3382 return nullptr;
3383 }
3384
3385 matrixA[0] = 1;
3386 matrixA[1] = 0;
3387 matrixA[2] = 0;
3388 matrixA[3] = 1;
3389 matrixA[4] = 0;
3390 matrixA[5] = 0;
3391 obj1 = dict->lookup(key: "Matrix");
3392 if (obj1.isArray() && obj1.arrayGetLength() == 6) {
3393 for (i = 0; i < 6; ++i) {
3394 Object obj2 = obj1.arrayGet(i);
3395 if (obj2.isNum()) {
3396 matrixA[i] = obj2.getNum();
3397 }
3398 }
3399 }
3400
3401 return new GfxShadingPattern(shadingA, matrixA, patternRefNum);
3402}
3403
3404GfxShadingPattern::GfxShadingPattern(GfxShading *shadingA, const double *matrixA, int patternRefNumA) : GfxPattern(2, patternRefNumA)
3405{
3406 int i;
3407
3408 shading = shadingA;
3409 for (i = 0; i < 6; ++i) {
3410 matrix[i] = matrixA[i];
3411 }
3412}
3413
3414GfxShadingPattern::~GfxShadingPattern()
3415{
3416 delete shading;
3417}
3418
3419GfxPattern *GfxShadingPattern::copy() const
3420{
3421 return new GfxShadingPattern(shading->copy(), matrix, getPatternRefNum());
3422}
3423
3424//------------------------------------------------------------------------
3425// GfxShading
3426//------------------------------------------------------------------------
3427
3428GfxShading::GfxShading(int typeA)
3429{
3430 type = typeA;
3431 colorSpace = nullptr;
3432}
3433
3434GfxShading::GfxShading(const GfxShading *shading)
3435{
3436 int i;
3437
3438 type = shading->type;
3439 colorSpace = shading->colorSpace->copy();
3440 for (i = 0; i < gfxColorMaxComps; ++i) {
3441 background.c[i] = shading->background.c[i];
3442 }
3443 hasBackground = shading->hasBackground;
3444 bbox_xMin = shading->bbox_xMin;
3445 bbox_yMin = shading->bbox_yMin;
3446 bbox_xMax = shading->bbox_xMax;
3447 bbox_yMax = shading->bbox_yMax;
3448 hasBBox = shading->hasBBox;
3449}
3450
3451GfxShading::~GfxShading()
3452{
3453 if (colorSpace) {
3454 delete colorSpace;
3455 }
3456}
3457
3458GfxShading *GfxShading::parse(GfxResources *res, Object *obj, OutputDev *out, GfxState *state)
3459{
3460 GfxShading *shading;
3461 Dict *dict;
3462 int typeA;
3463 Object obj1;
3464
3465 if (obj->isDict()) {
3466 dict = obj->getDict();
3467 } else if (obj->isStream()) {
3468 dict = obj->streamGetDict();
3469 } else {
3470 return nullptr;
3471 }
3472
3473 obj1 = dict->lookup(key: "ShadingType");
3474 if (!obj1.isInt()) {
3475 error(category: errSyntaxWarning, pos: -1, msg: "Invalid ShadingType in shading dictionary");
3476 return nullptr;
3477 }
3478 typeA = obj1.getInt();
3479
3480 switch (typeA) {
3481 case 1:
3482 shading = GfxFunctionShading::parse(res, dict, out, state);
3483 break;
3484 case 2:
3485 shading = GfxAxialShading::parse(res, dict, out, state);
3486 break;
3487 case 3:
3488 shading = GfxRadialShading::parse(res, dict, out, state);
3489 break;
3490 case 4:
3491 if (obj->isStream()) {
3492 shading = GfxGouraudTriangleShading::parse(res, typeA: 4, dict, str: obj->getStream(), out, state);
3493 } else {
3494 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Type 4 shading object");
3495 goto err1;
3496 }
3497 break;
3498 case 5:
3499 if (obj->isStream()) {
3500 shading = GfxGouraudTriangleShading::parse(res, typeA: 5, dict, str: obj->getStream(), out, state);
3501 } else {
3502 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Type 5 shading object");
3503 goto err1;
3504 }
3505 break;
3506 case 6:
3507 if (obj->isStream()) {
3508 shading = GfxPatchMeshShading::parse(res, typeA: 6, dict, str: obj->getStream(), out, state);
3509 } else {
3510 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Type 6 shading object");
3511 goto err1;
3512 }
3513 break;
3514 case 7:
3515 if (obj->isStream()) {
3516 shading = GfxPatchMeshShading::parse(res, typeA: 7, dict, str: obj->getStream(), out, state);
3517 } else {
3518 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Type 7 shading object");
3519 goto err1;
3520 }
3521 break;
3522 default:
3523 error(category: errSyntaxWarning, pos: -1, msg: "Unimplemented shading type {0:d}", typeA);
3524 goto err1;
3525 }
3526
3527 return shading;
3528
3529err1:
3530 return nullptr;
3531}
3532
3533bool GfxShading::init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
3534{
3535 Object obj1;
3536 int i;
3537
3538 obj1 = dict->lookup(key: "ColorSpace");
3539 if (!(colorSpace = GfxColorSpace::parse(res, csObj: &obj1, out, state))) {
3540 error(category: errSyntaxWarning, pos: -1, msg: "Bad color space in shading dictionary");
3541 return false;
3542 }
3543
3544 for (i = 0; i < gfxColorMaxComps; ++i) {
3545 background.c[i] = 0;
3546 }
3547 hasBackground = false;
3548 obj1 = dict->lookup(key: "Background");
3549 if (obj1.isArray()) {
3550 if (obj1.arrayGetLength() == colorSpace->getNComps()) {
3551 hasBackground = true;
3552 for (i = 0; i < colorSpace->getNComps(); ++i) {
3553 Object obj2 = obj1.arrayGet(i);
3554 background.c[i] = dblToCol(x: obj2.getNum(ok: &hasBackground));
3555 }
3556 if (!hasBackground) {
3557 error(category: errSyntaxWarning, pos: -1, msg: "Bad Background in shading dictionary");
3558 }
3559 } else {
3560 error(category: errSyntaxWarning, pos: -1, msg: "Bad Background in shading dictionary");
3561 }
3562 }
3563
3564 bbox_xMin = bbox_yMin = bbox_xMax = bbox_yMax = 0;
3565 hasBBox = false;
3566 obj1 = dict->lookup(key: "BBox");
3567 if (obj1.isArray()) {
3568 if (obj1.arrayGetLength() == 4) {
3569 hasBBox = true;
3570 bbox_xMin = obj1.arrayGet(i: 0).getNum(ok: &hasBBox);
3571 bbox_yMin = obj1.arrayGet(i: 1).getNum(ok: &hasBBox);
3572 bbox_xMax = obj1.arrayGet(i: 2).getNum(ok: &hasBBox);
3573 bbox_yMax = obj1.arrayGet(i: 3).getNum(ok: &hasBBox);
3574 if (!hasBBox) {
3575 error(category: errSyntaxWarning, pos: -1, msg: "Bad BBox in shading dictionary (Values not numbers)");
3576 }
3577 } else {
3578 error(category: errSyntaxWarning, pos: -1, msg: "Bad BBox in shading dictionary");
3579 }
3580 }
3581
3582 return true;
3583}
3584
3585//------------------------------------------------------------------------
3586// GfxFunctionShading
3587//------------------------------------------------------------------------
3588
3589GfxFunctionShading::GfxFunctionShading(double x0A, double y0A, double x1A, double y1A, const double *matrixA, std::vector<std::unique_ptr<Function>> &&funcsA) : GfxShading(1), funcs(std::move(funcsA))
3590{
3591 x0 = x0A;
3592 y0 = y0A;
3593 x1 = x1A;
3594 y1 = y1A;
3595 for (int i = 0; i < 6; ++i) {
3596 matrix[i] = matrixA[i];
3597 }
3598}
3599
3600GfxFunctionShading::GfxFunctionShading(const GfxFunctionShading *shading) : GfxShading(shading)
3601{
3602 x0 = shading->x0;
3603 y0 = shading->y0;
3604 x1 = shading->x1;
3605 y1 = shading->y1;
3606 for (int i = 0; i < 6; ++i) {
3607 matrix[i] = shading->matrix[i];
3608 }
3609 for (const auto &f : shading->funcs) {
3610 funcs.emplace_back(args: f->copy());
3611 }
3612}
3613
3614GfxFunctionShading::~GfxFunctionShading() { }
3615
3616GfxFunctionShading *GfxFunctionShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
3617{
3618 GfxFunctionShading *shading;
3619 double x0A, y0A, x1A, y1A;
3620 double matrixA[6];
3621 std::vector<std::unique_ptr<Function>> funcsA;
3622 Object obj1;
3623 int i;
3624
3625 x0A = y0A = 0;
3626 x1A = y1A = 1;
3627 obj1 = dict->lookup(key: "Domain");
3628 if (obj1.isArray() && obj1.arrayGetLength() == 4) {
3629 bool decodeOk = true;
3630 x0A = obj1.arrayGet(i: 0).getNum(ok: &decodeOk);
3631 x1A = obj1.arrayGet(i: 1).getNum(ok: &decodeOk);
3632 y0A = obj1.arrayGet(i: 2).getNum(ok: &decodeOk);
3633 y1A = obj1.arrayGet(i: 3).getNum(ok: &decodeOk);
3634
3635 if (!decodeOk) {
3636 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Domain array in function shading dictionary");
3637 return nullptr;
3638 }
3639 }
3640
3641 matrixA[0] = 1;
3642 matrixA[1] = 0;
3643 matrixA[2] = 0;
3644 matrixA[3] = 1;
3645 matrixA[4] = 0;
3646 matrixA[5] = 0;
3647 obj1 = dict->lookup(key: "Matrix");
3648 if (obj1.isArray() && obj1.arrayGetLength() == 6) {
3649 bool decodeOk = true;
3650 matrixA[0] = obj1.arrayGet(i: 0).getNum(ok: &decodeOk);
3651 matrixA[1] = obj1.arrayGet(i: 1).getNum(ok: &decodeOk);
3652 matrixA[2] = obj1.arrayGet(i: 2).getNum(ok: &decodeOk);
3653 matrixA[3] = obj1.arrayGet(i: 3).getNum(ok: &decodeOk);
3654 matrixA[4] = obj1.arrayGet(i: 4).getNum(ok: &decodeOk);
3655 matrixA[5] = obj1.arrayGet(i: 5).getNum(ok: &decodeOk);
3656
3657 if (!decodeOk) {
3658 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Matrix array in function shading dictionary");
3659 return nullptr;
3660 }
3661 }
3662
3663 obj1 = dict->lookup(key: "Function");
3664 if (obj1.isArray()) {
3665 const int nFuncsA = obj1.arrayGetLength();
3666 if (nFuncsA > gfxColorMaxComps || nFuncsA <= 0) {
3667 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Function array in shading dictionary");
3668 return nullptr;
3669 }
3670 for (i = 0; i < nFuncsA; ++i) {
3671 Object obj2 = obj1.arrayGet(i);
3672 Function *f = Function::parse(funcObj: &obj2);
3673 if (!f) {
3674 return nullptr;
3675 }
3676 funcsA.emplace_back(args&: f);
3677 }
3678 } else {
3679 Function *f = Function::parse(funcObj: &obj1);
3680 if (!f) {
3681 return nullptr;
3682 }
3683 funcsA.emplace_back(args&: f);
3684 }
3685
3686 shading = new GfxFunctionShading(x0A, y0A, x1A, y1A, matrixA, std::move(funcsA));
3687 if (!shading->init(res, dict, out, state)) {
3688 delete shading;
3689 return nullptr;
3690 }
3691 return shading;
3692}
3693
3694bool GfxFunctionShading::init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
3695{
3696 const bool parentInit = GfxShading::init(res, dict, out, state);
3697 if (!parentInit) {
3698 return false;
3699 }
3700
3701 // funcs needs to be one of the two:
3702 // * One function 2-in -> nComps-out
3703 // * nComps functions 2-in -> 1-out
3704 const int nComps = colorSpace->getNComps();
3705 const int nFuncs = funcs.size();
3706 if (nFuncs == 1) {
3707 if (funcs[0]->getInputSize() != 2) {
3708 error(category: errSyntaxWarning, pos: -1, msg: "GfxFunctionShading: function with input size != 2");
3709 return false;
3710 }
3711 if (funcs[0]->getOutputSize() != nComps) {
3712 error(category: errSyntaxWarning, pos: -1, msg: "GfxFunctionShading: function with wrong output size");
3713 return false;
3714 }
3715 } else if (nFuncs == nComps) {
3716 for (const std::unique_ptr<Function> &f : funcs) {
3717 if (f->getInputSize() != 2) {
3718 error(category: errSyntaxWarning, pos: -1, msg: "GfxFunctionShading: function with input size != 2");
3719 return false;
3720 }
3721 if (f->getOutputSize() != 1) {
3722 error(category: errSyntaxWarning, pos: -1, msg: "GfxFunctionShading: function with wrong output size");
3723 return false;
3724 }
3725 }
3726 } else {
3727 return false;
3728 }
3729
3730 return true;
3731}
3732
3733GfxShading *GfxFunctionShading::copy() const
3734{
3735 return new GfxFunctionShading(this);
3736}
3737
3738void GfxFunctionShading::getColor(double x, double y, GfxColor *color) const
3739{
3740 double in[2], out[gfxColorMaxComps];
3741
3742 // NB: there can be one function with n outputs or n functions with
3743 // one output each (where n = number of color components)
3744 for (double &i : out) {
3745 i = 0;
3746 }
3747 in[0] = x;
3748 in[1] = y;
3749 for (int i = 0; i < getNFuncs(); ++i) {
3750 funcs[i]->transform(in, out: &out[i]);
3751 }
3752 for (int i = 0; i < gfxColorMaxComps; ++i) {
3753 color->c[i] = dblToCol(x: out[i]);
3754 }
3755}
3756
3757//------------------------------------------------------------------------
3758// GfxUnivariateShading
3759//------------------------------------------------------------------------
3760
3761GfxUnivariateShading::GfxUnivariateShading(int typeA, double t0A, double t1A, std::vector<std::unique_ptr<Function>> &&funcsA, bool extend0A, bool extend1A) : GfxShading(typeA), funcs(std::move(funcsA))
3762{
3763 t0 = t0A;
3764 t1 = t1A;
3765 extend0 = extend0A;
3766 extend1 = extend1A;
3767
3768 cacheSize = 0;
3769 lastMatch = 0;
3770 cacheBounds = nullptr;
3771 cacheCoeff = nullptr;
3772 cacheValues = nullptr;
3773}
3774
3775GfxUnivariateShading::GfxUnivariateShading(const GfxUnivariateShading *shading) : GfxShading(shading)
3776{
3777 t0 = shading->t0;
3778 t1 = shading->t1;
3779 for (const auto &f : shading->funcs) {
3780 funcs.emplace_back(args: f->copy());
3781 }
3782 extend0 = shading->extend0;
3783 extend1 = shading->extend1;
3784
3785 cacheSize = 0;
3786 lastMatch = 0;
3787 cacheBounds = nullptr;
3788 cacheCoeff = nullptr;
3789 cacheValues = nullptr;
3790}
3791
3792GfxUnivariateShading::~GfxUnivariateShading()
3793{
3794 gfree(p: cacheBounds);
3795}
3796
3797int GfxUnivariateShading::getColor(double t, GfxColor *color)
3798{
3799 double out[gfxColorMaxComps];
3800
3801 // NB: there can be one function with n outputs or n functions with
3802 // one output each (where n = number of color components)
3803 const int nComps = getNFuncs() * funcs[0]->getOutputSize();
3804
3805 if (cacheSize > 0) {
3806 double x, ix, *l, *u, *upper;
3807
3808 if (cacheBounds[lastMatch - 1] >= t) {
3809 upper = std::lower_bound(first: cacheBounds, last: cacheBounds + lastMatch - 1, val: t);
3810 lastMatch = static_cast<int>(upper - cacheBounds);
3811 lastMatch = std::min<int>(a: std::max<int>(a: 1, b: lastMatch), b: cacheSize - 1);
3812 } else if (cacheBounds[lastMatch] < t) {
3813 upper = std::lower_bound(first: cacheBounds + lastMatch + 1, last: cacheBounds + cacheSize, val: t);
3814 lastMatch = static_cast<int>(upper - cacheBounds);
3815 lastMatch = std::min<int>(a: std::max<int>(a: 1, b: lastMatch), b: cacheSize - 1);
3816 }
3817
3818 x = (t - cacheBounds[lastMatch - 1]) * cacheCoeff[lastMatch];
3819 ix = 1.0 - x;
3820 u = cacheValues + lastMatch * nComps;
3821 l = u - nComps;
3822
3823 for (int i = 0; i < nComps; ++i) {
3824 out[i] = ix * l[i] + x * u[i];
3825 }
3826 } else {
3827 for (int i = 0; i < nComps; ++i) {
3828 out[i] = 0;
3829 }
3830 for (int i = 0; i < getNFuncs(); ++i) {
3831 funcs[i]->transform(in: &t, out: &out[i]);
3832 }
3833 }
3834
3835 for (int i = 0; i < nComps; ++i) {
3836 color->c[i] = dblToCol(x: out[i]);
3837 }
3838 return nComps;
3839}
3840
3841void GfxUnivariateShading::setupCache(const Matrix *ctm, double xMin, double yMin, double xMax, double yMax)
3842{
3843 double sMin, sMax, tMin, tMax, upperBound;
3844 int i, j, nComps, maxSize;
3845
3846 gfree(p: cacheBounds);
3847 cacheBounds = nullptr;
3848 cacheSize = 0;
3849
3850 if (unlikely(getNFuncs() < 1)) {
3851 return;
3852 }
3853
3854 // NB: there can be one function with n outputs or n functions with
3855 // one output each (where n = number of color components)
3856 nComps = getNFuncs() * funcs[0]->getOutputSize();
3857
3858 getParameterRange(lower: &sMin, upper: &sMax, xMin, yMin, xMax, yMax);
3859 upperBound = ctm->norm() * getDistance(sMin, sMax);
3860 maxSize = static_cast<int>(ceil(x: upperBound));
3861 maxSize = std::max<int>(a: maxSize, b: 2);
3862
3863 {
3864 double x[4], y[4];
3865
3866 ctm->transform(x: xMin, y: yMin, tx: &x[0], ty: &y[0]);
3867 ctm->transform(x: xMax, y: yMin, tx: &x[1], ty: &y[1]);
3868 ctm->transform(x: xMin, y: yMax, tx: &x[2], ty: &y[2]);
3869 ctm->transform(x: xMax, y: yMax, tx: &x[3], ty: &y[3]);
3870
3871 xMin = xMax = x[0];
3872 yMin = yMax = y[0];
3873 for (i = 1; i < 4; i++) {
3874 xMin = std::min<double>(a: xMin, b: x[i]);
3875 yMin = std::min<double>(a: yMin, b: y[i]);
3876 xMax = std::max<double>(a: xMax, b: x[i]);
3877 yMax = std::max<double>(a: yMax, b: y[i]);
3878 }
3879 }
3880
3881 if (maxSize > (xMax - xMin) * (yMax - yMin)) {
3882 return;
3883 }
3884
3885 if (t0 < t1) {
3886 tMin = t0 + sMin * (t1 - t0);
3887 tMax = t0 + sMax * (t1 - t0);
3888 } else {
3889 tMin = t0 + sMax * (t1 - t0);
3890 tMax = t0 + sMin * (t1 - t0);
3891 }
3892
3893 cacheBounds = (double *)gmallocn_checkoverflow(count: maxSize, size: sizeof(double) * (nComps + 2));
3894 if (unlikely(!cacheBounds)) {
3895 return;
3896 }
3897 cacheCoeff = cacheBounds + maxSize;
3898 cacheValues = cacheCoeff + maxSize;
3899
3900 if (cacheSize != 0) {
3901 for (j = 0; j < cacheSize; ++j) {
3902 cacheCoeff[j] = 1 / (cacheBounds[j + 1] - cacheBounds[j]);
3903 }
3904 } else if (tMax != tMin) {
3905 double step = (tMax - tMin) / (maxSize - 1);
3906 double coeff = (maxSize - 1) / (tMax - tMin);
3907
3908 cacheSize = maxSize;
3909
3910 for (j = 0; j < cacheSize; ++j) {
3911 cacheBounds[j] = tMin + j * step;
3912 cacheCoeff[j] = coeff;
3913
3914 for (i = 0; i < nComps; ++i) {
3915 cacheValues[j * nComps + i] = 0;
3916 }
3917 for (i = 0; i < getNFuncs(); ++i) {
3918 funcs[i]->transform(in: &cacheBounds[j], out: &cacheValues[j * nComps + i]);
3919 }
3920 }
3921 }
3922
3923 lastMatch = 1;
3924}
3925
3926bool GfxUnivariateShading::init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
3927{
3928 const bool parentInit = GfxShading::init(res, dict, out, state);
3929 if (!parentInit) {
3930 return false;
3931 }
3932
3933 // funcs needs to be one of the two:
3934 // * One function 1-in -> nComps-out
3935 // * nComps functions 1-in -> 1-out
3936 const int nComps = colorSpace->getNComps();
3937 const int nFuncs = funcs.size();
3938 if (nFuncs == 1) {
3939 if (funcs[0]->getInputSize() != 1) {
3940 error(category: errSyntaxWarning, pos: -1, msg: "GfxUnivariateShading: function with input size != 2");
3941 return false;
3942 }
3943 if (funcs[0]->getOutputSize() != nComps) {
3944 error(category: errSyntaxWarning, pos: -1, msg: "GfxUnivariateShading: function with wrong output size");
3945 return false;
3946 }
3947 } else if (nFuncs == nComps) {
3948 for (const std::unique_ptr<Function> &f : funcs) {
3949 if (f->getInputSize() != 1) {
3950 error(category: errSyntaxWarning, pos: -1, msg: "GfxUnivariateShading: function with input size != 2");
3951 return false;
3952 }
3953 if (f->getOutputSize() != 1) {
3954 error(category: errSyntaxWarning, pos: -1, msg: "GfxUnivariateShading: function with wrong output size");
3955 return false;
3956 }
3957 }
3958 } else {
3959 return false;
3960 }
3961
3962 return true;
3963}
3964
3965//------------------------------------------------------------------------
3966// GfxAxialShading
3967//------------------------------------------------------------------------
3968
3969GfxAxialShading::GfxAxialShading(double x0A, double y0A, double x1A, double y1A, double t0A, double t1A, std::vector<std::unique_ptr<Function>> &&funcsA, bool extend0A, bool extend1A)
3970 : GfxUnivariateShading(2, t0A, t1A, std::move(funcsA), extend0A, extend1A)
3971{
3972 x0 = x0A;
3973 y0 = y0A;
3974 x1 = x1A;
3975 y1 = y1A;
3976}
3977
3978GfxAxialShading::GfxAxialShading(const GfxAxialShading *shading) : GfxUnivariateShading(shading)
3979{
3980 x0 = shading->x0;
3981 y0 = shading->y0;
3982 x1 = shading->x1;
3983 y1 = shading->y1;
3984}
3985
3986GfxAxialShading::~GfxAxialShading() { }
3987
3988GfxAxialShading *GfxAxialShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state)
3989{
3990 GfxAxialShading *shading;
3991 double x0A, y0A, x1A, y1A;
3992 double t0A, t1A;
3993 std::vector<std::unique_ptr<Function>> funcsA;
3994 bool extend0A, extend1A;
3995 Object obj1;
3996
3997 x0A = y0A = x1A = y1A = 0;
3998 obj1 = dict->lookup(key: "Coords");
3999 if (obj1.isArray() && obj1.arrayGetLength() == 4) {
4000 x0A = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
4001 y0A = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 0);
4002 x1A = obj1.arrayGet(i: 2).getNumWithDefaultValue(defaultValue: 0);
4003 y1A = obj1.arrayGet(i: 3).getNumWithDefaultValue(defaultValue: 0);
4004 } else {
4005 error(category: errSyntaxWarning, pos: -1, msg: "Missing or invalid Coords in shading dictionary");
4006 return nullptr;
4007 }
4008
4009 t0A = 0;
4010 t1A = 1;
4011 obj1 = dict->lookup(key: "Domain");
4012 if (obj1.isArray() && obj1.arrayGetLength() == 2) {
4013 t0A = obj1.arrayGet(i: 0).getNumWithDefaultValue(defaultValue: 0);
4014 t1A = obj1.arrayGet(i: 1).getNumWithDefaultValue(defaultValue: 1);
4015 }
4016
4017 obj1 = dict->lookup(key: "Function");
4018 if (obj1.isArray()) {
4019 const int nFuncsA = obj1.arrayGetLength();
4020 if (nFuncsA > gfxColorMaxComps || nFuncsA == 0) {
4021 error(category: errSyntaxWarning, pos: -1, msg: "Invalid Function array in shading dictionary");
4022 return nullptr;
4023 }
4024 for (int i = 0; i < nFuncsA; ++i) {
4025 Object obj2 = obj1.arrayGet(i);
4026 Function *f = Function::parse(funcObj: &obj2);
4027 if (!f) {
4028 return nullptr;
4029 }
4030 funcsA.emplace_back(args&: f);
4031 }
4032 } else {
4033 Function *f = Function::parse(funcObj: &obj1);
4034 if (!f) {
4035 return nullptr;
4036 }
4037 funcsA.emplace_back(args&: f);
4038 }
4039
4040 extend0A = extend1A = false;
4041 obj1 = dict->lookup(key: "Extend");
4042 if (obj1.isArray() && obj1.arrayGetLength() == 2) {
4043 Object obj2 = obj1.arrayGet(i: 0);
4044 if (obj2.isBool()) {
4045 extend0A = obj2.getBool();
4046 } else {
4047 error(category: errSyntaxWarning, pos: -1, msg: "Invalid axial shading extend (0)");
4048 }
4049 obj2 = obj1.arrayGet(i: 1);
4050 if (obj2.isBool()) {
4051 extend1A = obj2.getBool();
4052 } else {
4053 error(category: errSyntaxWarning, pos: -1, msg: "Invalid axial shading extend (1)");
4054 }
4055 }
4056
4057 shading = new GfxAxialShading(x0A, y0A, x1A, y1A, t0A, t1A, std::move(funcsA), extend0A, extend1A);
4058 if (!shading->init(res, dict, out, state)) {
4059 delete shading;
4060 shading = nullptr;
4061 }
4062 return shading;
4063}
4064
4065GfxShading *GfxAxialShading::copy() const
4066{
4067 return new GfxAxialShading(this);
4068}
4069
4070double GfxAxialShading::getDistance(double sMin, double sMax) const
4071{
4072 double xMin, yMin, xMax, yMax;
4073