1 //---------------------------------------------------------------------------------
2 //
3 // Little Color Management System
4 // Copyright (c) 1998-2023 Marti Maria Saguer
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the Software
11 // is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24 //---------------------------------------------------------------------------------
25 //
26
27 #include "lcms2_internal.h"
28
29 // PostScript ColorRenderingDictionary and ColorSpaceArray
30
31
32 #define MAXPSCOLS 60 // Columns on tables
33
34 /*
35 Implementation
36 --------------
37
38 PostScript does use XYZ as its internal PCS. But since PostScript
39 interpolation tables are limited to 8 bits, I use Lab as a way to
40 improve the accuracy, favoring perceptual results. So, for the creation
41 of each CRD, CSA the profiles are converted to Lab via a device
42 link between profile -> Lab or Lab -> profile. The PS code necessary to
43 convert Lab <-> XYZ is also included.
44
45
46
47 Color Space Arrays (CSA)
48 ==================================================================================
49
50 In order to obtain precision, code chooses between three ways to implement
51 the device -> XYZ transform. These cases identifies monochrome profiles (often
52 implemented as a set of curves), matrix-shaper and Pipeline-based.
53
54 Monochrome
55 -----------
56
57 This is implemented as /CIEBasedA CSA. The prelinearization curve is
58 placed into /DecodeA section, and matrix equals to D50. Since here is
59 no interpolation tables, I do the conversion directly to XYZ
60
61 NOTE: CLUT-based monochrome profiles are NOT supported. So, cmsFLAGS_MATRIXINPUT
62 flag is forced on such profiles.
63
64 [ /CIEBasedA
65 <<
66 /DecodeA { transfer function } bind
67 /MatrixA [D50]
68 /RangeLMN [ 0.0 cmsD50X 0.0 cmsD50Y 0.0 cmsD50Z ]
69 /WhitePoint [D50]
70 /BlackPoint [BP]
71 /RenderingIntent (intent)
72 >>
73 ]
74
75 On simpler profiles, the PCS is already XYZ, so no conversion is required.
76
77
78 Matrix-shaper based
79 -------------------
80
81 This is implemented both with /CIEBasedABC or /CIEBasedDEF depending on the
82 profile implementation. Since here there are no interpolation tables, I do
83 the conversion directly to XYZ
84
85
86
87 [ /CIEBasedABC
88 <<
89 /DecodeABC [ {transfer1} {transfer2} {transfer3} ]
90 /MatrixABC [Matrix]
91 /RangeLMN [ 0.0 cmsD50X 0.0 cmsD50Y 0.0 cmsD50Z ]
92 /DecodeLMN [ { / 2} dup dup ]
93 /WhitePoint [D50]
94 /BlackPoint [BP]
95 /RenderingIntent (intent)
96 >>
97 ]
98
99
100 CLUT based
101 ----------
102
103 Lab is used in such cases.
104
105 [ /CIEBasedDEF
106 <<
107 /DecodeDEF [ <prelinearization> ]
108 /Table [ p p p [<...>]]
109 /RangeABC [ 0 1 0 1 0 1]
110 /DecodeABC[ <postlinearization> ]
111 /RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]
112 % -128/500 1+127/500 0 1 -127/200 1+128/200
113 /MatrixABC [ 1 1 1 1 0 0 0 0 -1]
114 /WhitePoint [D50]
115 /BlackPoint [BP]
116 /RenderingIntent (intent)
117 ]
118
119
120 Color Rendering Dictionaries (CRD)
121 ==================================
122 These are always implemented as CLUT, and always are using Lab. Since CRD are expected to
123 be used as resources, the code adds the definition as well.
124
125 <<
126 /ColorRenderingType 1
127 /WhitePoint [ D50 ]
128 /BlackPoint [BP]
129 /MatrixPQR [ Bradford ]
130 /RangePQR [-0.125 1.375 -0.125 1.375 -0.125 1.375 ]
131 /TransformPQR [
132 {4 index 3 get div 2 index 3 get mul exch pop exch pop exch pop exch pop } bind
133 {4 index 4 get div 2 index 4 get mul exch pop exch pop exch pop exch pop } bind
134 {4 index 5 get div 2 index 5 get mul exch pop exch pop exch pop exch pop } bind
135 ]
136 /MatrixABC <...>
137 /EncodeABC <...>
138 /RangeABC <.. used for XYZ -> Lab>
139 /EncodeLMN
140 /RenderTable [ p p p [<...>]]
141
142 /RenderingIntent (Perceptual)
143 >>
144 /Current exch /ColorRendering defineresource pop
145
146
147 The following stages are used to convert from XYZ to Lab
148 --------------------------------------------------------
149
150 Input is given at LMN stage on X, Y, Z
151
152 Encode LMN gives us f(X/Xn), f(Y/Yn), f(Z/Zn)
153
154 /EncodeLMN [
155
156 { 0.964200 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
157 { 1.000000 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
158 { 0.824900 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
159
160 ]
161
162
163 MatrixABC is used to compute f(Y/Yn), f(X/Xn) - f(Y/Yn), f(Y/Yn) - f(Z/Zn)
164
165 | 0 1 0|
166 | 1 -1 0|
167 | 0 1 -1|
168
169 /MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]
170
171 EncodeABC finally gives Lab values.
172
173 /EncodeABC [
174 { 116 mul 16 sub 100 div } bind
175 { 500 mul 128 add 255 div } bind
176 { 200 mul 128 add 255 div } bind
177 ]
178
179 The following stages are used to convert Lab to XYZ
180 ----------------------------------------------------
181
182 /RangeABC [ 0 1 0 1 0 1]
183 /DecodeABC [ { 100 mul 16 add 116 div } bind
184 { 255 mul 128 sub 500 div } bind
185 { 255 mul 128 sub 200 div } bind
186 ]
187
188 /MatrixABC [ 1 1 1 1 0 0 0 0 -1]
189 /DecodeLMN [
190 {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.964200 mul} bind
191 {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind
192 {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.824900 mul} bind
193 ]
194
195
196 */
197
198 /*
199
200 PostScript algorithms discussion.
201 =========================================================================================================
202
203 1D interpolation algorithm
204
205
206 1D interpolation (float)
207 ------------------------
208
209 val2 = Domain * Value;
210
211 cell0 = (int) floor(val2);
212 cell1 = (int) ceil(val2);
213
214 rest = val2 - cell0;
215
216 y0 = LutTable[cell0] ;
217 y1 = LutTable[cell1] ;
218
219 y = y0 + (y1 - y0) * rest;
220
221
222
223 PostScript code Stack
224 ================================================
225
226 { % v
227 <check 0..1.0>
228 [array] % v tab
229 dup % v tab tab
230 length 1 sub % v tab dom
231
232 3 -1 roll % tab dom v
233
234 mul % tab val2
235 dup % tab val2 val2
236 dup % tab val2 val2 val2
237 floor cvi % tab val2 val2 cell0
238 exch % tab val2 cell0 val2
239 ceiling cvi % tab val2 cell0 cell1
240
241 3 index % tab val2 cell0 cell1 tab
242 exch % tab val2 cell0 tab cell1
243 get % tab val2 cell0 y1
244
245 4 -1 roll % val2 cell0 y1 tab
246 3 -1 roll % val2 y1 tab cell0
247 get % val2 y1 y0
248
249 dup % val2 y1 y0 y0
250 3 1 roll % val2 y0 y1 y0
251
252 sub % val2 y0 (y1-y0)
253 3 -1 roll % y0 (y1-y0) val2
254 dup % y0 (y1-y0) val2 val2
255 floor cvi % y0 (y1-y0) val2 floor(val2)
256 sub % y0 (y1-y0) rest
257 mul % y0 t1
258 add % y
259 65535 div % result
260
261 } bind
262
263
264 */
265
266
267 // This struct holds the memory block currently being write
268 typedef struct {
269 _cmsStageCLutData* Pipeline;
270 cmsIOHANDLER* m;
271
272 int FirstComponent;
273 int SecondComponent;
274
275 const char* PreMaj;
276 const char* PostMaj;
277 const char* PreMin;
278 const char* PostMin;
279
280 int FixWhite; // Force mapping of pure white
281
282 cmsColorSpaceSignature ColorSpace; // ColorSpace of profile
283
284
285 } cmsPsSamplerCargo;
286
287 static int _cmsPSActualColumn = 0;
288
289
290 // Convert to byte
291 static
Word2Byte(cmsUInt16Number w)292 cmsUInt8Number Word2Byte(cmsUInt16Number w)
293 {
294 return (cmsUInt8Number) floor((cmsFloat64Number) w / 257.0 + 0.5);
295 }
296
297
298 // Write a cooked byte
299 static
WriteByte(cmsIOHANDLER * m,cmsUInt8Number b)300 void WriteByte(cmsIOHANDLER* m, cmsUInt8Number b)
301 {
302 _cmsIOPrintf(m, "%02x", b);
303 _cmsPSActualColumn += 2;
304
305 if (_cmsPSActualColumn > MAXPSCOLS) {
306
307 _cmsIOPrintf(m, "\n");
308 _cmsPSActualColumn = 0;
309 }
310 }
311
312 // ----------------------------------------------------------------- PostScript generation
313
314
315 // Removes offending carriage returns
316
317 static
RemoveCR(const char * txt)318 char* RemoveCR(const char* txt)
319 {
320 static char Buffer[2048];
321 char* pt;
322
323 strncpy(Buffer, txt, 2047);
324 Buffer[2047] = 0;
325 for (pt = Buffer; *pt; pt++)
326 if (*pt == '\n' || *pt == '\r') *pt = ' ';
327
328 return Buffer;
329
330 }
331
332 static
EmitHeader(cmsIOHANDLER * m,const char * Title,cmsHPROFILE hProfile)333 void EmitHeader(cmsIOHANDLER* m, const char* Title, cmsHPROFILE hProfile)
334 {
335 time_t timer;
336 cmsMLU *Description, *Copyright;
337 char DescASCII[256], CopyrightASCII[256];
338
339 time(&timer);
340
341 Description = (cmsMLU*) cmsReadTag(hProfile, cmsSigProfileDescriptionTag);
342 Copyright = (cmsMLU*) cmsReadTag(hProfile, cmsSigCopyrightTag);
343
344 DescASCII[0] = DescASCII[255] = 0;
345 CopyrightASCII[0] = CopyrightASCII[255] = 0;
346
347 if (Description != NULL) cmsMLUgetASCII(Description, cmsNoLanguage, cmsNoCountry, DescASCII, 255);
348 if (Copyright != NULL) cmsMLUgetASCII(Copyright, cmsNoLanguage, cmsNoCountry, CopyrightASCII, 255);
349
350 _cmsIOPrintf(m, "%%!PS-Adobe-3.0\n");
351 _cmsIOPrintf(m, "%%\n");
352 _cmsIOPrintf(m, "%% %s\n", Title);
353 _cmsIOPrintf(m, "%% Source: %s\n", RemoveCR(DescASCII));
354 _cmsIOPrintf(m, "%% %s\n", RemoveCR(CopyrightASCII));
355 _cmsIOPrintf(m, "%% Created: %s", ctime(&timer)); // ctime appends a \n!!!
356 _cmsIOPrintf(m, "%%\n");
357 _cmsIOPrintf(m, "%%%%BeginResource\n");
358
359 }
360
361
362 // Emits White & Black point. White point is always D50, Black point is the device
363 // Black point adapted to D50.
364
365 static
EmitWhiteBlackD50(cmsIOHANDLER * m,cmsCIEXYZ * BlackPoint)366 void EmitWhiteBlackD50(cmsIOHANDLER* m, cmsCIEXYZ* BlackPoint)
367 {
368
369 _cmsIOPrintf(m, "/BlackPoint [%f %f %f]\n", BlackPoint -> X,
370 BlackPoint -> Y,
371 BlackPoint -> Z);
372
373 _cmsIOPrintf(m, "/WhitePoint [%f %f %f]\n", cmsD50_XYZ()->X,
374 cmsD50_XYZ()->Y,
375 cmsD50_XYZ()->Z);
376 }
377
378
379 static
EmitRangeCheck(cmsIOHANDLER * m)380 void EmitRangeCheck(cmsIOHANDLER* m)
381 {
382 _cmsIOPrintf(m, "dup 0.0 lt { pop 0.0 } if "
383 "dup 1.0 gt { pop 1.0 } if ");
384
385 }
386
387 // Does write the intent
388
389 static
EmitIntent(cmsIOHANDLER * m,cmsUInt32Number RenderingIntent)390 void EmitIntent(cmsIOHANDLER* m, cmsUInt32Number RenderingIntent)
391 {
392 const char *intent;
393
394 switch (RenderingIntent) {
395
396 case INTENT_PERCEPTUAL: intent = "Perceptual"; break;
397 case INTENT_RELATIVE_COLORIMETRIC: intent = "RelativeColorimetric"; break;
398 case INTENT_ABSOLUTE_COLORIMETRIC: intent = "AbsoluteColorimetric"; break;
399 case INTENT_SATURATION: intent = "Saturation"; break;
400
401 default: intent = "Undefined"; break;
402 }
403
404 _cmsIOPrintf(m, "/RenderingIntent (%s)\n", intent );
405 }
406
407 //
408 // Convert L* to Y
409 //
410 // Y = Yn*[ (L* + 16) / 116] ^ 3 if (L*) >= 6 / 29
411 // = Yn*( L* / 116) / 7.787 if (L*) < 6 / 29
412 //
413
414 // Lab -> XYZ, see the discussion above
415
416 static
EmitLab2XYZ(cmsIOHANDLER * m)417 void EmitLab2XYZ(cmsIOHANDLER* m)
418 {
419 _cmsIOPrintf(m, "/RangeABC [ 0 1 0 1 0 1]\n");
420 _cmsIOPrintf(m, "/DecodeABC [\n");
421 _cmsIOPrintf(m, "{100 mul 16 add 116 div } bind\n");
422 _cmsIOPrintf(m, "{255 mul 128 sub 500 div } bind\n");
423 _cmsIOPrintf(m, "{255 mul 128 sub 200 div } bind\n");
424 _cmsIOPrintf(m, "]\n");
425 _cmsIOPrintf(m, "/MatrixABC [ 1 1 1 1 0 0 0 0 -1]\n");
426 _cmsIOPrintf(m, "/RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]\n");
427 _cmsIOPrintf(m, "/DecodeLMN [\n");
428 _cmsIOPrintf(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.964200 mul} bind\n");
429 _cmsIOPrintf(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind\n");
430 _cmsIOPrintf(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.824900 mul} bind\n");
431 _cmsIOPrintf(m, "]\n");
432 }
433
434 static
EmitSafeGuardBegin(cmsIOHANDLER * m,const char * name)435 void EmitSafeGuardBegin(cmsIOHANDLER* m, const char* name)
436 {
437 _cmsIOPrintf(m, "%%LCMS2: Save previous definition of %s on the operand stack\n", name);
438 _cmsIOPrintf(m, "currentdict /%s known { /%s load } { null } ifelse\n", name, name);
439 }
440
441 static
EmitSafeGuardEnd(cmsIOHANDLER * m,const char * name,int depth)442 void EmitSafeGuardEnd(cmsIOHANDLER* m, const char* name, int depth)
443 {
444 _cmsIOPrintf(m, "%%LCMS2: Restore previous definition of %s\n", name);
445 if (depth > 1) {
446 // cycle topmost items on the stack to bring the previous definition to the front
447 _cmsIOPrintf(m, "%d -1 roll ", depth);
448 }
449 _cmsIOPrintf(m, "dup null eq { pop currentdict /%s undef } { /%s exch def } ifelse\n", name, name);
450 }
451
452 // Outputs a table of words. It does use 16 bits
453
454 static
Emit1Gamma(cmsIOHANDLER * m,cmsToneCurve * Table,const char * name)455 void Emit1Gamma(cmsIOHANDLER* m, cmsToneCurve* Table, const char* name)
456 {
457 cmsUInt32Number i;
458 cmsFloat64Number gamma;
459
460 if (Table == NULL) return; // Error
461
462 if (Table ->nEntries <= 0) return; // Empty table
463
464 // Suppress whole if identity
465 if (cmsIsToneCurveLinear(Table)) return;
466
467 // Check if is really an exponential. If so, emit "exp"
468 gamma = cmsEstimateGamma(Table, 0.001);
469 if (gamma > 0) {
470 _cmsIOPrintf(m, "/%s { %g exp } bind def\n", name, gamma);
471 return;
472 }
473
474 EmitSafeGuardBegin(m, "lcms2gammatable");
475 _cmsIOPrintf(m, "/lcms2gammatable [");
476
477 for (i=0; i < Table->nEntries; i++) {
478 if (i % 10 == 0)
479 _cmsIOPrintf(m, "\n ");
480 _cmsIOPrintf(m, "%d ", Table->Table16[i]);
481 }
482
483 _cmsIOPrintf(m, "] def\n");
484
485
486 // Emit interpolation code
487
488 // PostScript code Stack
489 // =============== ========================
490 // v
491 _cmsIOPrintf(m, "/%s {\n ", name);
492
493 // Bounds check
494 EmitRangeCheck(m);
495
496 _cmsIOPrintf(m, "\n //lcms2gammatable "); // v tab
497 _cmsIOPrintf(m, "dup "); // v tab tab
498 _cmsIOPrintf(m, "length 1 sub "); // v tab dom
499 _cmsIOPrintf(m, "3 -1 roll "); // tab dom v
500 _cmsIOPrintf(m, "mul "); // tab val2
501 _cmsIOPrintf(m, "dup "); // tab val2 val2
502 _cmsIOPrintf(m, "dup "); // tab val2 val2 val2
503 _cmsIOPrintf(m, "floor cvi "); // tab val2 val2 cell0
504 _cmsIOPrintf(m, "exch "); // tab val2 cell0 val2
505 _cmsIOPrintf(m, "ceiling cvi "); // tab val2 cell0 cell1
506 _cmsIOPrintf(m, "3 index "); // tab val2 cell0 cell1 tab
507 _cmsIOPrintf(m, "exch "); // tab val2 cell0 tab cell1
508 _cmsIOPrintf(m, "get\n "); // tab val2 cell0 y1
509 _cmsIOPrintf(m, "4 -1 roll "); // val2 cell0 y1 tab
510 _cmsIOPrintf(m, "3 -1 roll "); // val2 y1 tab cell0
511 _cmsIOPrintf(m, "get "); // val2 y1 y0
512 _cmsIOPrintf(m, "dup "); // val2 y1 y0 y0
513 _cmsIOPrintf(m, "3 1 roll "); // val2 y0 y1 y0
514 _cmsIOPrintf(m, "sub "); // val2 y0 (y1-y0)
515 _cmsIOPrintf(m, "3 -1 roll "); // y0 (y1-y0) val2
516 _cmsIOPrintf(m, "dup "); // y0 (y1-y0) val2 val2
517 _cmsIOPrintf(m, "floor cvi "); // y0 (y1-y0) val2 floor(val2)
518 _cmsIOPrintf(m, "sub "); // y0 (y1-y0) rest
519 _cmsIOPrintf(m, "mul "); // y0 t1
520 _cmsIOPrintf(m, "add "); // y
521 _cmsIOPrintf(m, "65535 div\n"); // result
522
523 _cmsIOPrintf(m, "} bind def\n");
524
525 EmitSafeGuardEnd(m, "lcms2gammatable", 1);
526 }
527
528
529 // Compare gamma table
530
531 static
GammaTableEquals(cmsUInt16Number * g1,cmsUInt16Number * g2,cmsUInt32Number nG1,cmsUInt32Number nG2)532 cmsBool GammaTableEquals(cmsUInt16Number* g1, cmsUInt16Number* g2, cmsUInt32Number nG1, cmsUInt32Number nG2)
533 {
534 if (nG1 != nG2) return FALSE;
535 return memcmp(g1, g2, nG1 * sizeof(cmsUInt16Number)) == 0;
536 }
537
538
539 // Does write a set of gamma curves
540
541 static
EmitNGamma(cmsIOHANDLER * m,cmsUInt32Number n,cmsToneCurve * g[],const char * nameprefix)542 void EmitNGamma(cmsIOHANDLER* m, cmsUInt32Number n, cmsToneCurve* g[], const char* nameprefix)
543 {
544 cmsUInt32Number i;
545 static char buffer[2048];
546
547 for( i=0; i < n; i++ )
548 {
549 if (g[i] == NULL) return; // Error
550
551 if (i > 0 && GammaTableEquals(g[i-1]->Table16, g[i]->Table16, g[i-1]->nEntries, g[i]->nEntries)) {
552
553 _cmsIOPrintf(m, "/%s%d /%s%d load def\n", nameprefix, i, nameprefix, i-1);
554 }
555 else {
556 snprintf(buffer, sizeof(buffer), "%s%d", nameprefix, (int) i);
557 buffer[sizeof(buffer)-1] = '\0';
558 Emit1Gamma(m, g[i], buffer);
559 }
560 }
561
562 }
563
564
565 // Following code dumps a LUT onto memory stream
566
567
568 // This is the sampler. Intended to work in SAMPLER_INSPECT mode,
569 // that is, the callback will be called for each knot with
570 //
571 // In[] The grid location coordinates, normalized to 0..ffff
572 // Out[] The Pipeline values, normalized to 0..ffff
573 //
574 // Returning a value other than 0 does terminate the sampling process
575 //
576 // Each row contains Pipeline values for all but first component. So, I
577 // detect row changing by keeping a copy of last value of first
578 // component. -1 is used to mark beginning of whole block.
579
580 static
OutputValueSampler(CMSREGISTER const cmsUInt16Number In[],CMSREGISTER cmsUInt16Number Out[],CMSREGISTER void * Cargo)581 int OutputValueSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)
582 {
583 cmsPsSamplerCargo* sc = (cmsPsSamplerCargo*) Cargo;
584 cmsUInt32Number i;
585
586
587 if (sc -> FixWhite) {
588
589 if (In[0] == 0xFFFF) { // Only in L* = 100, ab = [-8..8]
590
591 if ((In[1] >= 0x7800 && In[1] <= 0x8800) &&
592 (In[2] >= 0x7800 && In[2] <= 0x8800)) {
593
594 cmsUInt16Number* Black;
595 cmsUInt16Number* White;
596 cmsUInt32Number nOutputs;
597
598 if (!_cmsEndPointsBySpace(sc ->ColorSpace, &White, &Black, &nOutputs))
599 return 0;
600
601 for (i=0; i < nOutputs; i++)
602 Out[i] = White[i];
603 }
604
605
606 }
607 }
608
609
610 // Hadle the parenthesis on rows
611
612 if (In[0] != sc ->FirstComponent) {
613
614 if (sc ->FirstComponent != -1) {
615
616 _cmsIOPrintf(sc ->m, sc ->PostMin);
617 sc ->SecondComponent = -1;
618 _cmsIOPrintf(sc ->m, sc ->PostMaj);
619 }
620
621 // Begin block
622 _cmsPSActualColumn = 0;
623
624 _cmsIOPrintf(sc ->m, sc ->PreMaj);
625 sc ->FirstComponent = In[0];
626 }
627
628
629 if (In[1] != sc ->SecondComponent) {
630
631 if (sc ->SecondComponent != -1) {
632
633 _cmsIOPrintf(sc ->m, sc ->PostMin);
634 }
635
636 _cmsIOPrintf(sc ->m, sc ->PreMin);
637 sc ->SecondComponent = In[1];
638 }
639
640 // Dump table.
641
642 for (i=0; i < sc -> Pipeline ->Params->nOutputs; i++) {
643
644 cmsUInt16Number wWordOut = Out[i];
645 cmsUInt8Number wByteOut; // Value as byte
646
647
648 // We always deal with Lab4
649
650 wByteOut = Word2Byte(wWordOut);
651 WriteByte(sc -> m, wByteOut);
652 }
653
654 return 1;
655 }
656
657 // Writes a Pipeline on memstream. Could be 8 or 16 bits based
658
659 static
WriteCLUT(cmsIOHANDLER * m,cmsStage * mpe,const char * PreMaj,const char * PostMaj,const char * PreMin,const char * PostMin,int FixWhite,cmsColorSpaceSignature ColorSpace)660 void WriteCLUT(cmsIOHANDLER* m, cmsStage* mpe, const char* PreMaj,
661 const char* PostMaj,
662 const char* PreMin,
663 const char* PostMin,
664 int FixWhite,
665 cmsColorSpaceSignature ColorSpace)
666 {
667 cmsUInt32Number i;
668 cmsPsSamplerCargo sc;
669
670 sc.FirstComponent = -1;
671 sc.SecondComponent = -1;
672 sc.Pipeline = (_cmsStageCLutData *) mpe ->Data;
673 sc.m = m;
674 sc.PreMaj = PreMaj;
675 sc.PostMaj= PostMaj;
676
677 sc.PreMin = PreMin;
678 sc.PostMin = PostMin;
679 sc.FixWhite = FixWhite;
680 sc.ColorSpace = ColorSpace;
681
682 _cmsIOPrintf(m, "[");
683
684 for (i=0; i < sc.Pipeline->Params->nInputs; i++)
685 _cmsIOPrintf(m, " %d ", sc.Pipeline->Params->nSamples[i]);
686
687 _cmsIOPrintf(m, " [\n");
688
689 cmsStageSampleCLut16bit(mpe, OutputValueSampler, (void*) &sc, SAMPLER_INSPECT);
690
691 _cmsIOPrintf(m, PostMin);
692 _cmsIOPrintf(m, PostMaj);
693 _cmsIOPrintf(m, "] ");
694
695 }
696
697
698 // Dumps CIEBasedA Color Space Array
699
700 static
EmitCIEBasedA(cmsIOHANDLER * m,cmsToneCurve * Curve,cmsCIEXYZ * BlackPoint)701 int EmitCIEBasedA(cmsIOHANDLER* m, cmsToneCurve* Curve, cmsCIEXYZ* BlackPoint)
702 {
703
704 _cmsIOPrintf(m, "[ /CIEBasedA\n");
705 _cmsIOPrintf(m, " <<\n");
706
707 EmitSafeGuardBegin(m, "lcms2gammaproc");
708 Emit1Gamma(m, Curve, "lcms2gammaproc");
709
710 _cmsIOPrintf(m, "/DecodeA /lcms2gammaproc load\n");
711 EmitSafeGuardEnd(m, "lcms2gammaproc", 3);
712
713 _cmsIOPrintf(m, "/MatrixA [ 0.9642 1.0000 0.8249 ]\n");
714 _cmsIOPrintf(m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
715
716 EmitWhiteBlackD50(m, BlackPoint);
717 EmitIntent(m, INTENT_PERCEPTUAL);
718
719 _cmsIOPrintf(m, ">>\n");
720 _cmsIOPrintf(m, "]\n");
721
722 return 1;
723 }
724
725
726 // Dumps CIEBasedABC Color Space Array
727
728 static
EmitCIEBasedABC(cmsIOHANDLER * m,cmsFloat64Number * Matrix,cmsToneCurve ** CurveSet,cmsCIEXYZ * BlackPoint)729 int EmitCIEBasedABC(cmsIOHANDLER* m, cmsFloat64Number* Matrix, cmsToneCurve** CurveSet, cmsCIEXYZ* BlackPoint)
730 {
731 int i;
732
733 _cmsIOPrintf(m, "[ /CIEBasedABC\n");
734 _cmsIOPrintf(m, "<<\n");
735
736 EmitSafeGuardBegin(m, "lcms2gammaproc0");
737 EmitSafeGuardBegin(m, "lcms2gammaproc1");
738 EmitSafeGuardBegin(m, "lcms2gammaproc2");
739 EmitNGamma(m, 3, CurveSet, "lcms2gammaproc");
740 _cmsIOPrintf(m, "/DecodeABC [\n");
741 _cmsIOPrintf(m, " /lcms2gammaproc0 load\n");
742 _cmsIOPrintf(m, " /lcms2gammaproc1 load\n");
743 _cmsIOPrintf(m, " /lcms2gammaproc2 load\n");
744 _cmsIOPrintf(m, "]\n");
745 EmitSafeGuardEnd(m, "lcms2gammaproc2", 3);
746 EmitSafeGuardEnd(m, "lcms2gammaproc1", 3);
747 EmitSafeGuardEnd(m, "lcms2gammaproc0", 3);
748
749 _cmsIOPrintf(m, "/MatrixABC [ " );
750
751 for( i=0; i < 3; i++ ) {
752
753 _cmsIOPrintf(m, "%.6f %.6f %.6f ", Matrix[i + 3*0],
754 Matrix[i + 3*1],
755 Matrix[i + 3*2]);
756 }
757
758
759 _cmsIOPrintf(m, "]\n");
760
761 _cmsIOPrintf(m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
762
763 EmitWhiteBlackD50(m, BlackPoint);
764 EmitIntent(m, INTENT_PERCEPTUAL);
765
766 _cmsIOPrintf(m, ">>\n");
767 _cmsIOPrintf(m, "]\n");
768
769
770 return 1;
771 }
772
773
774 static
EmitCIEBasedDEF(cmsIOHANDLER * m,cmsPipeline * Pipeline,cmsUInt32Number Intent,cmsCIEXYZ * BlackPoint)775 int EmitCIEBasedDEF(cmsIOHANDLER* m, cmsPipeline* Pipeline, cmsUInt32Number Intent, cmsCIEXYZ* BlackPoint)
776 {
777 const char* PreMaj;
778 const char* PostMaj;
779 const char* PreMin, * PostMin;
780 cmsStage* mpe;
781 int i, numchans;
782 static char buffer[2048];
783
784 mpe = Pipeline->Elements;
785
786 switch (cmsStageInputChannels(mpe)) {
787 case 3:
788 _cmsIOPrintf(m, "[ /CIEBasedDEF\n");
789 PreMaj = "<";
790 PostMaj = ">\n";
791 PreMin = PostMin = "";
792 break;
793
794 case 4:
795 _cmsIOPrintf(m, "[ /CIEBasedDEFG\n");
796 PreMaj = "[";
797 PostMaj = "]\n";
798 PreMin = "<";
799 PostMin = ">\n";
800 break;
801
802 default:
803 return 0;
804
805 }
806
807 _cmsIOPrintf(m, "<<\n");
808
809 if (cmsStageType(mpe) == cmsSigCurveSetElemType) {
810
811 numchans = (int) cmsStageOutputChannels(mpe);
812 for (i = 0; i < numchans; ++i) {
813 snprintf(buffer, sizeof(buffer), "lcms2gammaproc%d", i);
814 buffer[sizeof(buffer) - 1] = '\0';
815 EmitSafeGuardBegin(m, buffer);
816 }
817 EmitNGamma(m, cmsStageOutputChannels(mpe), _cmsStageGetPtrToCurveSet(mpe), "lcms2gammaproc");
818 _cmsIOPrintf(m, "/DecodeDEF [\n");
819 for (i = 0; i < numchans; ++i) {
820 snprintf(buffer, sizeof(buffer), " /lcms2gammaproc%d load\n", i);
821 buffer[sizeof(buffer) - 1] = '\0';
822 _cmsIOPrintf(m, buffer);
823 }
824 _cmsIOPrintf(m, "]\n");
825 for (i = numchans - 1; i >= 0; --i) {
826 snprintf(buffer, sizeof(buffer), "lcms2gammaproc%d", i);
827 buffer[sizeof(buffer) - 1] = '\0';
828 EmitSafeGuardEnd(m, buffer, 3);
829 }
830
831 mpe = mpe->Next;
832 }
833
834 if (cmsStageType(mpe) == cmsSigCLutElemType) {
835
836 _cmsIOPrintf(m, "/Table ");
837 WriteCLUT(m, mpe, PreMaj, PostMaj, PreMin, PostMin, FALSE, (cmsColorSpaceSignature)0);
838 _cmsIOPrintf(m, "]\n");
839 }
840
841 EmitLab2XYZ(m);
842 EmitWhiteBlackD50(m, BlackPoint);
843 EmitIntent(m, Intent);
844
845 _cmsIOPrintf(m, " >>\n");
846 _cmsIOPrintf(m, "]\n");
847
848 return 1;
849 }
850
851 // Generates a curve from a gray profile
852
853 static
ExtractGray2Y(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent)854 cmsToneCurve* ExtractGray2Y(cmsContext ContextID, cmsHPROFILE hProfile, cmsUInt32Number Intent)
855 {
856 cmsToneCurve* Out = cmsBuildTabulatedToneCurve16(ContextID, 256, NULL);
857 cmsHPROFILE hXYZ = cmsCreateXYZProfile();
858 cmsHTRANSFORM xform = cmsCreateTransformTHR(ContextID, hProfile, TYPE_GRAY_8, hXYZ, TYPE_XYZ_DBL, Intent, cmsFLAGS_NOOPTIMIZE);
859 int i;
860
861 if (Out != NULL && xform != NULL) {
862 for (i=0; i < 256; i++) {
863
864 cmsUInt8Number Gray = (cmsUInt8Number) i;
865 cmsCIEXYZ XYZ;
866
867 cmsDoTransform(xform, &Gray, &XYZ, 1);
868
869 Out ->Table16[i] =_cmsQuickSaturateWord(XYZ.Y * 65535.0);
870 }
871 }
872
873 if (xform) cmsDeleteTransform(xform);
874 if (hXYZ) cmsCloseProfile(hXYZ);
875 return Out;
876 }
877
878
879
880 // Because PostScript has only 8 bits in /Table, we should use
881 // a more perceptually uniform space... I do choose Lab.
882
883 static
WriteInputLUT(cmsIOHANDLER * m,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags)884 int WriteInputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
885 {
886 cmsHPROFILE hLab;
887 cmsHTRANSFORM xform;
888 cmsUInt32Number nChannels;
889 cmsUInt32Number InputFormat;
890 int rc;
891 cmsHPROFILE Profiles[2];
892 cmsCIEXYZ BlackPointAdaptedToD50;
893
894 // Does create a device-link based transform.
895 // The DeviceLink is next dumped as working CSA.
896
897 InputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE);
898 nChannels = T_CHANNELS(InputFormat);
899
900
901 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0);
902
903 // Adjust output to Lab4
904 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
905
906 Profiles[0] = hProfile;
907 Profiles[1] = hLab;
908
909 xform = cmsCreateMultiprofileTransform(Profiles, 2, InputFormat, TYPE_Lab_DBL, Intent, 0);
910 cmsCloseProfile(hLab);
911
912 if (xform == NULL) {
913
914 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Profile -> Lab");
915 return 0;
916 }
917
918 // Only 1, 3 and 4 channels are allowed
919
920 switch (nChannels) {
921
922 case 1: {
923 cmsToneCurve* Gray2Y = ExtractGray2Y(m ->ContextID, hProfile, Intent);
924 EmitCIEBasedA(m, Gray2Y, &BlackPointAdaptedToD50);
925 cmsFreeToneCurve(Gray2Y);
926 }
927 break;
928
929 case 3:
930 case 4: {
931 cmsUInt32Number OutFrm = TYPE_Lab_16;
932 cmsPipeline* DeviceLink;
933 _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform;
934
935 DeviceLink = cmsPipelineDup(v ->Lut);
936 if (DeviceLink == NULL) return 0;
937
938 dwFlags |= cmsFLAGS_FORCE_CLUT;
939 _cmsOptimizePipeline(m->ContextID, &DeviceLink, Intent, &InputFormat, &OutFrm, &dwFlags);
940
941 rc = EmitCIEBasedDEF(m, DeviceLink, Intent, &BlackPointAdaptedToD50);
942 cmsPipelineFree(DeviceLink);
943 if (rc == 0) return 0;
944 }
945 break;
946
947 default:
948
949 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Only 3, 4 channels are supported for CSA. This profile has %d channels.", nChannels);
950 return 0;
951 }
952
953
954 cmsDeleteTransform(xform);
955
956 return 1;
957 }
958
959 static
GetPtrToMatrix(const cmsStage * mpe)960 cmsFloat64Number* GetPtrToMatrix(const cmsStage* mpe)
961 {
962 _cmsStageMatrixData* Data = (_cmsStageMatrixData*) mpe ->Data;
963
964 return Data -> Double;
965 }
966
967
968 // Does create CSA based on matrix-shaper. Allowed types are gray and RGB based
969 static
WriteInputMatrixShaper(cmsIOHANDLER * m,cmsHPROFILE hProfile,cmsStage * Matrix,cmsStage * Shaper)970 int WriteInputMatrixShaper(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsStage* Matrix, cmsStage* Shaper)
971 {
972 cmsColorSpaceSignature ColorSpace;
973 int rc;
974 cmsCIEXYZ BlackPointAdaptedToD50;
975
976 ColorSpace = cmsGetColorSpace(hProfile);
977
978 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0);
979
980 if (ColorSpace == cmsSigGrayData) {
981
982 cmsToneCurve** ShaperCurve = _cmsStageGetPtrToCurveSet(Shaper);
983 rc = EmitCIEBasedA(m, ShaperCurve[0], &BlackPointAdaptedToD50);
984
985 }
986 else
987 if (ColorSpace == cmsSigRgbData) {
988
989 cmsMAT3 Mat;
990 int i, j;
991
992 memmove(&Mat, GetPtrToMatrix(Matrix), sizeof(Mat));
993
994 for (i = 0; i < 3; i++)
995 for (j = 0; j < 3; j++)
996 Mat.v[i].n[j] *= MAX_ENCODEABLE_XYZ;
997
998 rc = EmitCIEBasedABC(m, (cmsFloat64Number *)&Mat,
999 _cmsStageGetPtrToCurveSet(Shaper),
1000 &BlackPointAdaptedToD50);
1001 }
1002 else {
1003
1004 cmsSignalError(m->ContextID, cmsERROR_COLORSPACE_CHECK, "Profile is not suitable for CSA. Unsupported colorspace.");
1005 return 0;
1006 }
1007
1008 return rc;
1009 }
1010
1011
1012
1013 // Creates a PostScript color list from a named profile data.
1014 // This is a HP extension, and it works in Lab instead of XYZ
1015
1016 static
WriteNamedColorCSA(cmsIOHANDLER * m,cmsHPROFILE hNamedColor,cmsUInt32Number Intent)1017 int WriteNamedColorCSA(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent)
1018 {
1019 cmsHTRANSFORM xform;
1020 cmsHPROFILE hLab;
1021 cmsUInt32Number i, nColors;
1022 char ColorName[cmsMAX_PATH];
1023 cmsNAMEDCOLORLIST* NamedColorList;
1024
1025 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
1026 xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, hLab, TYPE_Lab_DBL, Intent, 0);
1027 if (xform == NULL) return 0;
1028
1029 NamedColorList = cmsGetNamedColorList(xform);
1030 if (NamedColorList == NULL) return 0;
1031
1032 _cmsIOPrintf(m, "<<\n");
1033 _cmsIOPrintf(m, "(colorlistcomment) (%s)\n", "Named color CSA");
1034 _cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
1035 _cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");
1036
1037 nColors = cmsNamedColorCount(NamedColorList);
1038
1039
1040 for (i=0; i < nColors; i++) {
1041
1042 cmsUInt16Number In[1];
1043 cmsCIELab Lab;
1044
1045 In[0] = (cmsUInt16Number) i;
1046
1047 if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL))
1048 continue;
1049
1050 cmsDoTransform(xform, In, &Lab, 1);
1051 _cmsIOPrintf(m, " (%s) [ %.3f %.3f %.3f ]\n", ColorName, Lab.L, Lab.a, Lab.b);
1052 }
1053
1054
1055
1056 _cmsIOPrintf(m, ">>\n");
1057
1058 cmsDeleteTransform(xform);
1059 cmsCloseProfile(hLab);
1060 return 1;
1061 }
1062
1063
1064 // Does create a Color Space Array on XYZ colorspace for PostScript usage
1065 static
GenerateCSA(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,cmsIOHANDLER * mem)1066 cmsUInt32Number GenerateCSA(cmsContext ContextID,
1067 cmsHPROFILE hProfile,
1068 cmsUInt32Number Intent,
1069 cmsUInt32Number dwFlags,
1070 cmsIOHANDLER* mem)
1071 {
1072 cmsUInt32Number dwBytesUsed;
1073 cmsPipeline* lut = NULL;
1074 cmsStage* Matrix, *Shaper;
1075
1076
1077 // Is a named color profile?
1078 if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
1079
1080 if (!WriteNamedColorCSA(mem, hProfile, Intent)) goto Error;
1081 }
1082 else {
1083
1084
1085 // Any profile class are allowed (including devicelink), but
1086 // output (PCS) colorspace must be XYZ or Lab
1087 cmsColorSpaceSignature ColorSpace = cmsGetPCS(hProfile);
1088
1089 if (ColorSpace != cmsSigXYZData &&
1090 ColorSpace != cmsSigLabData) {
1091
1092 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Invalid output color space");
1093 goto Error;
1094 }
1095
1096
1097 // Read the lut with all necessary conversion stages
1098 lut = _cmsReadInputLUT(hProfile, Intent);
1099 if (lut == NULL) goto Error;
1100
1101
1102 // Tone curves + matrix can be implemented without any LUT
1103 if (cmsPipelineCheckAndRetreiveStages(lut, 2, cmsSigCurveSetElemType, cmsSigMatrixElemType, &Shaper, &Matrix)) {
1104
1105 if (!WriteInputMatrixShaper(mem, hProfile, Matrix, Shaper)) goto Error;
1106
1107 }
1108 else {
1109 // We need a LUT for the rest
1110 if (!WriteInputLUT(mem, hProfile, Intent, dwFlags)) goto Error;
1111 }
1112 }
1113
1114
1115 // Done, keep memory usage
1116 dwBytesUsed = mem ->UsedSpace;
1117
1118 // Get rid of LUT
1119 if (lut != NULL) cmsPipelineFree(lut);
1120
1121 // Finally, return used byte count
1122 return dwBytesUsed;
1123
1124 Error:
1125 if (lut != NULL) cmsPipelineFree(lut);
1126 return 0;
1127 }
1128
1129 // ------------------------------------------------------ Color Rendering Dictionary (CRD)
1130
1131
1132
1133 /*
1134
1135 Black point compensation plus chromatic adaptation:
1136
1137 Step 1 - Chromatic adaptation
1138 =============================
1139
1140 WPout
1141 X = ------- PQR
1142 Wpin
1143
1144 Step 2 - Black point compensation
1145 =================================
1146
1147 (WPout - BPout)*X - WPout*(BPin - BPout)
1148 out = ---------------------------------------
1149 WPout - BPin
1150
1151
1152 Algorithm discussion
1153 ====================
1154
1155 TransformPQR(WPin, BPin, WPout, BPout, PQR)
1156
1157 Wpin,etc= { Xws Yws Zws Pws Qws Rws }
1158
1159
1160 Algorithm Stack 0...n
1161 ===========================================================
1162 PQR BPout WPout BPin WPin
1163 4 index 3 get WPin PQR BPout WPout BPin WPin
1164 div (PQR/WPin) BPout WPout BPin WPin
1165 2 index 3 get WPout (PQR/WPin) BPout WPout BPin WPin
1166 mult WPout*(PQR/WPin) BPout WPout BPin WPin
1167
1168 2 index 3 get WPout WPout*(PQR/WPin) BPout WPout BPin WPin
1169 2 index 3 get BPout WPout WPout*(PQR/WPin) BPout WPout BPin WPin
1170 sub (WPout-BPout) WPout*(PQR/WPin) BPout WPout BPin WPin
1171 mult (WPout-BPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1172
1173 2 index 3 get WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1174 4 index 3 get BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1175 3 index 3 get BPout BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1176
1177 sub (BPin-BPout) WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1178 mult (BPin-BPout)*WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1179 sub (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1180
1181 3 index 3 get BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1182 3 index 3 get WPout BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1183 exch
1184 sub (WPout-BPin) (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1185 div
1186
1187 exch pop
1188 exch pop
1189 exch pop
1190 exch pop
1191
1192 */
1193
1194
1195 static
EmitPQRStage(cmsIOHANDLER * m,cmsHPROFILE hProfile,int DoBPC,int lIsAbsolute)1196 void EmitPQRStage(cmsIOHANDLER* m, cmsHPROFILE hProfile, int DoBPC, int lIsAbsolute)
1197 {
1198
1199
1200 if (lIsAbsolute) {
1201
1202 // For absolute colorimetric intent, encode back to relative
1203 // and generate a relative Pipeline
1204
1205 // Relative encoding is obtained across XYZpcs*(D50/WhitePoint)
1206
1207 cmsCIEXYZ White;
1208
1209 _cmsReadMediaWhitePoint(&White, hProfile);
1210
1211 _cmsIOPrintf(m,"/MatrixPQR [1 0 0 0 1 0 0 0 1 ]\n");
1212 _cmsIOPrintf(m,"/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");
1213
1214 _cmsIOPrintf(m, "%% Absolute colorimetric -- encode to relative to maximize LUT usage\n"
1215 "/TransformPQR [\n"
1216 "{0.9642 mul %g div exch pop exch pop exch pop exch pop} bind\n"
1217 "{1.0000 mul %g div exch pop exch pop exch pop exch pop} bind\n"
1218 "{0.8249 mul %g div exch pop exch pop exch pop exch pop} bind\n]\n",
1219 White.X, White.Y, White.Z);
1220 return;
1221 }
1222
1223
1224 _cmsIOPrintf(m,"%% Bradford Cone Space\n"
1225 "/MatrixPQR [0.8951 -0.7502 0.0389 0.2664 1.7135 -0.0685 -0.1614 0.0367 1.0296 ] \n");
1226
1227 _cmsIOPrintf(m, "/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");
1228
1229
1230 // No BPC
1231
1232 if (!DoBPC) {
1233
1234 _cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space\n"
1235 "/TransformPQR [\n"
1236 "{exch pop exch 3 get mul exch pop exch 3 get div} bind\n"
1237 "{exch pop exch 4 get mul exch pop exch 4 get div} bind\n"
1238 "{exch pop exch 5 get mul exch pop exch 5 get div} bind\n]\n");
1239 } else {
1240
1241 // BPC
1242
1243 _cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space plus BPC\n"
1244 "/TransformPQR [\n");
1245
1246 _cmsIOPrintf(m, "{4 index 3 get div 2 index 3 get mul "
1247 "2 index 3 get 2 index 3 get sub mul "
1248 "2 index 3 get 4 index 3 get 3 index 3 get sub mul sub "
1249 "3 index 3 get 3 index 3 get exch sub div "
1250 "exch pop exch pop exch pop exch pop } bind\n");
1251
1252 _cmsIOPrintf(m, "{4 index 4 get div 2 index 4 get mul "
1253 "2 index 4 get 2 index 4 get sub mul "
1254 "2 index 4 get 4 index 4 get 3 index 4 get sub mul sub "
1255 "3 index 4 get 3 index 4 get exch sub div "
1256 "exch pop exch pop exch pop exch pop } bind\n");
1257
1258 _cmsIOPrintf(m, "{4 index 5 get div 2 index 5 get mul "
1259 "2 index 5 get 2 index 5 get sub mul "
1260 "2 index 5 get 4 index 5 get 3 index 5 get sub mul sub "
1261 "3 index 5 get 3 index 5 get exch sub div "
1262 "exch pop exch pop exch pop exch pop } bind\n]\n");
1263
1264 }
1265 }
1266
1267
1268 static
EmitXYZ2Lab(cmsIOHANDLER * m)1269 void EmitXYZ2Lab(cmsIOHANDLER* m)
1270 {
1271 _cmsIOPrintf(m, "/RangeLMN [ -0.635 2.0 0 2 -0.635 2.0 ]\n");
1272 _cmsIOPrintf(m, "/EncodeLMN [\n");
1273 _cmsIOPrintf(m, "{ 0.964200 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1274 _cmsIOPrintf(m, "{ 1.000000 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1275 _cmsIOPrintf(m, "{ 0.824900 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1276 _cmsIOPrintf(m, "]\n");
1277 _cmsIOPrintf(m, "/MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]\n");
1278 _cmsIOPrintf(m, "/EncodeABC [\n");
1279
1280
1281 _cmsIOPrintf(m, "{ 116 mul 16 sub 100 div } bind\n");
1282 _cmsIOPrintf(m, "{ 500 mul 128 add 256 div } bind\n");
1283 _cmsIOPrintf(m, "{ 200 mul 128 add 256 div } bind\n");
1284
1285
1286 _cmsIOPrintf(m, "]\n");
1287
1288
1289 }
1290
1291 // Due to impedance mismatch between XYZ and almost all RGB and CMYK spaces
1292 // I choose to dump LUTS in Lab instead of XYZ. There is still a lot of wasted
1293 // space on 3D CLUT, but since space seems not to be a problem here, 33 points
1294 // would give a reasonable accuracy. Note also that CRD tables must operate in
1295 // 8 bits.
1296
1297 static
WriteOutputLUT(cmsIOHANDLER * m,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags)1298 int WriteOutputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
1299 {
1300 cmsHPROFILE hLab;
1301 cmsHTRANSFORM xform;
1302 cmsUInt32Number i, nChannels;
1303 cmsUInt32Number OutputFormat;
1304 _cmsTRANSFORM* v;
1305 cmsPipeline* DeviceLink;
1306 cmsHPROFILE Profiles[3];
1307 cmsCIEXYZ BlackPointAdaptedToD50;
1308 cmsBool lDoBPC = (cmsBool) (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION);
1309 cmsBool lFixWhite = (cmsBool) !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP);
1310 cmsUInt32Number InFrm = TYPE_Lab_16;
1311 cmsUInt32Number RelativeEncodingIntent;
1312 cmsColorSpaceSignature ColorSpace;
1313
1314
1315 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
1316 if (hLab == NULL) return 0;
1317
1318 OutputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE);
1319 nChannels = T_CHANNELS(OutputFormat);
1320
1321 ColorSpace = cmsGetColorSpace(hProfile);
1322
1323 // For absolute colorimetric, the LUT is encoded as relative in order to preserve precision.
1324
1325 RelativeEncodingIntent = Intent;
1326 if (RelativeEncodingIntent == INTENT_ABSOLUTE_COLORIMETRIC)
1327 RelativeEncodingIntent = INTENT_RELATIVE_COLORIMETRIC;
1328
1329
1330 // Use V4 Lab always
1331 Profiles[0] = hLab;
1332 Profiles[1] = hProfile;
1333
1334 xform = cmsCreateMultiprofileTransformTHR(m ->ContextID,
1335 Profiles, 2, TYPE_Lab_DBL,
1336 OutputFormat, RelativeEncodingIntent, 0);
1337 cmsCloseProfile(hLab);
1338
1339 if (xform == NULL) {
1340
1341 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Lab -> Profile in CRD creation");
1342 return 0;
1343 }
1344
1345 // Get a copy of the internal devicelink
1346 v = (_cmsTRANSFORM*) xform;
1347 DeviceLink = cmsPipelineDup(v ->Lut);
1348 if (DeviceLink == NULL) return 0;
1349
1350
1351 // We need a CLUT
1352 dwFlags |= cmsFLAGS_FORCE_CLUT;
1353 _cmsOptimizePipeline(m->ContextID, &DeviceLink, RelativeEncodingIntent, &InFrm, &OutputFormat, &dwFlags);
1354
1355 _cmsIOPrintf(m, "<<\n");
1356 _cmsIOPrintf(m, "/ColorRenderingType 1\n");
1357
1358
1359 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0);
1360
1361 // Emit headers, etc.
1362 EmitWhiteBlackD50(m, &BlackPointAdaptedToD50);
1363 EmitPQRStage(m, hProfile, lDoBPC, Intent == INTENT_ABSOLUTE_COLORIMETRIC);
1364 EmitXYZ2Lab(m);
1365
1366
1367 // FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab
1368 // does map a=b=0 not falling into any specific node. Since range a,b goes -128..127,
1369 // zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to
1370 // zero. This would sacrifice a bit of highlights, but failure to do so would cause
1371 // scum dot. Ouch.
1372
1373 if (Intent == INTENT_ABSOLUTE_COLORIMETRIC)
1374 lFixWhite = FALSE;
1375
1376 _cmsIOPrintf(m, "/RenderTable ");
1377
1378
1379 WriteCLUT(m, cmsPipelineGetPtrToFirstStage(DeviceLink), "<", ">\n", "", "", lFixWhite, ColorSpace);
1380
1381 _cmsIOPrintf(m, " %d {} bind ", nChannels);
1382
1383 for (i=1; i < nChannels; i++)
1384 _cmsIOPrintf(m, "dup ");
1385
1386 _cmsIOPrintf(m, "]\n");
1387
1388
1389 EmitIntent(m, Intent);
1390
1391 _cmsIOPrintf(m, ">>\n");
1392
1393 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1394
1395 _cmsIOPrintf(m, "/Current exch /ColorRendering defineresource pop\n");
1396 }
1397
1398 cmsPipelineFree(DeviceLink);
1399 cmsDeleteTransform(xform);
1400
1401 return 1;
1402 }
1403
1404
1405 // Builds a ASCII string containing colorant list in 0..1.0 range
1406 static
BuildColorantList(char * Colorant,cmsUInt32Number nColorant,cmsUInt16Number Out[])1407 void BuildColorantList(char *Colorant, cmsUInt32Number nColorant, cmsUInt16Number Out[])
1408 {
1409 char Buff[32];
1410 cmsUInt32Number j;
1411
1412 Colorant[0] = 0;
1413 if (nColorant > cmsMAXCHANNELS)
1414 nColorant = cmsMAXCHANNELS;
1415
1416 for (j = 0; j < nColorant; j++) {
1417
1418 snprintf(Buff, 31, "%.3f", Out[j] / 65535.0);
1419 Buff[31] = 0;
1420 strcat(Colorant, Buff);
1421 if (j < nColorant - 1)
1422 strcat(Colorant, " ");
1423
1424 }
1425 }
1426
1427
1428 // Creates a PostScript color list from a named profile data.
1429 // This is a HP extension.
1430
1431 static
WriteNamedColorCRD(cmsIOHANDLER * m,cmsHPROFILE hNamedColor,cmsUInt32Number Intent,cmsUInt32Number dwFlags)1432 int WriteNamedColorCRD(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
1433 {
1434 cmsHTRANSFORM xform;
1435 cmsUInt32Number i, nColors, nColorant;
1436 cmsUInt32Number OutputFormat;
1437 char ColorName[cmsMAX_PATH];
1438 char Colorant[512];
1439 cmsNAMEDCOLORLIST* NamedColorList;
1440
1441
1442 OutputFormat = cmsFormatterForColorspaceOfProfile(hNamedColor, 2, FALSE);
1443 nColorant = T_CHANNELS(OutputFormat);
1444
1445
1446 xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, NULL, OutputFormat, Intent, dwFlags);
1447 if (xform == NULL) return 0;
1448
1449
1450 NamedColorList = cmsGetNamedColorList(xform);
1451 if (NamedColorList == NULL) return 0;
1452
1453 _cmsIOPrintf(m, "<<\n");
1454 _cmsIOPrintf(m, "(colorlistcomment) (%s) \n", "Named profile");
1455 _cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
1456 _cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");
1457
1458 nColors = cmsNamedColorCount(NamedColorList);
1459
1460 for (i=0; i < nColors; i++) {
1461
1462 cmsUInt16Number In[1];
1463 cmsUInt16Number Out[cmsMAXCHANNELS];
1464
1465 In[0] = (cmsUInt16Number) i;
1466
1467 if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL))
1468 continue;
1469
1470 cmsDoTransform(xform, In, Out, 1);
1471 BuildColorantList(Colorant, nColorant, Out);
1472 _cmsIOPrintf(m, " (%s) [ %s ]\n", ColorName, Colorant);
1473 }
1474
1475 _cmsIOPrintf(m, " >>");
1476
1477 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1478
1479 _cmsIOPrintf(m, " /Current exch /HPSpotTable defineresource pop\n");
1480 }
1481
1482 cmsDeleteTransform(xform);
1483 return 1;
1484 }
1485
1486
1487
1488 // This one does create a Color Rendering Dictionary.
1489 // CRD are always LUT-Based, no matter if profile is
1490 // implemented as matrix-shaper.
1491
1492 static
GenerateCRD(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,cmsIOHANDLER * mem)1493 cmsUInt32Number GenerateCRD(cmsContext ContextID,
1494 cmsHPROFILE hProfile,
1495 cmsUInt32Number Intent, cmsUInt32Number dwFlags,
1496 cmsIOHANDLER* mem)
1497 {
1498 cmsUInt32Number dwBytesUsed;
1499
1500 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1501
1502 EmitHeader(mem, "Color Rendering Dictionary (CRD)", hProfile);
1503 }
1504
1505
1506 // Is a named color profile?
1507 if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
1508
1509 if (!WriteNamedColorCRD(mem, hProfile, Intent, dwFlags)) {
1510 return 0;
1511 }
1512 }
1513 else {
1514
1515 // CRD are always implemented as LUT
1516
1517 if (!WriteOutputLUT(mem, hProfile, Intent, dwFlags)) {
1518 return 0;
1519 }
1520 }
1521
1522 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1523
1524 _cmsIOPrintf(mem, "%%%%EndResource\n");
1525 _cmsIOPrintf(mem, "\n%% CRD End\n");
1526 }
1527
1528 // Done, keep memory usage
1529 dwBytesUsed = mem ->UsedSpace;
1530
1531 // Finally, return used byte count
1532 return dwBytesUsed;
1533
1534 cmsUNUSED_PARAMETER(ContextID);
1535 }
1536
1537
1538
1539
cmsGetPostScriptColorResource(cmsContext ContextID,cmsPSResourceType Type,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,cmsIOHANDLER * io)1540 cmsUInt32Number CMSEXPORT cmsGetPostScriptColorResource(cmsContext ContextID,
1541 cmsPSResourceType Type,
1542 cmsHPROFILE hProfile,
1543 cmsUInt32Number Intent,
1544 cmsUInt32Number dwFlags,
1545 cmsIOHANDLER* io)
1546 {
1547 cmsUInt32Number rc;
1548
1549
1550 switch (Type) {
1551
1552 case cmsPS_RESOURCE_CSA:
1553 rc = GenerateCSA(ContextID, hProfile, Intent, dwFlags, io);
1554 break;
1555
1556 default:
1557 case cmsPS_RESOURCE_CRD:
1558 rc = GenerateCRD(ContextID, hProfile, Intent, dwFlags, io);
1559 break;
1560 }
1561
1562 return rc;
1563 }
1564
1565
1566
cmsGetPostScriptCRD(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,void * Buffer,cmsUInt32Number dwBufferLen)1567 cmsUInt32Number CMSEXPORT cmsGetPostScriptCRD(cmsContext ContextID,
1568 cmsHPROFILE hProfile,
1569 cmsUInt32Number Intent, cmsUInt32Number dwFlags,
1570 void* Buffer, cmsUInt32Number dwBufferLen)
1571 {
1572 cmsIOHANDLER* mem;
1573 cmsUInt32Number dwBytesUsed;
1574
1575 // Set up the serialization engine
1576 if (Buffer == NULL)
1577 mem = cmsOpenIOhandlerFromNULL(ContextID);
1578 else
1579 mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w");
1580
1581 if (!mem) return 0;
1582
1583 dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CRD, hProfile, Intent, dwFlags, mem);
1584
1585 // Get rid of memory stream
1586 cmsCloseIOhandler(mem);
1587
1588 return dwBytesUsed;
1589 }
1590
1591
1592
1593 // Does create a Color Space Array on XYZ colorspace for PostScript usage
cmsGetPostScriptCSA(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,void * Buffer,cmsUInt32Number dwBufferLen)1594 cmsUInt32Number CMSEXPORT cmsGetPostScriptCSA(cmsContext ContextID,
1595 cmsHPROFILE hProfile,
1596 cmsUInt32Number Intent,
1597 cmsUInt32Number dwFlags,
1598 void* Buffer,
1599 cmsUInt32Number dwBufferLen)
1600 {
1601 cmsIOHANDLER* mem;
1602 cmsUInt32Number dwBytesUsed;
1603
1604 if (Buffer == NULL)
1605 mem = cmsOpenIOhandlerFromNULL(ContextID);
1606 else
1607 mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w");
1608
1609 if (!mem) return 0;
1610
1611 dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CSA, hProfile, Intent, dwFlags, mem);
1612
1613 // Get rid of memory stream
1614 cmsCloseIOhandler(mem);
1615
1616 return dwBytesUsed;
1617
1618 }
1619