1 //---------------------------------------------------------------------------------
2 //
3 // Little Color Management System
4 // Copyright (c) 1998-2016 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 on dependig
82 of 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 // Convert to byte (using ICC2 notation)
299 /*
300 static
301 cmsUInt8Number L2Byte(cmsUInt16Number w)
302 {
303 int ww = w + 0x0080;
304
305 if (ww > 0xFFFF) return 0xFF;
306
307 return (cmsUInt8Number) ((cmsUInt16Number) (ww >> 8) & 0xFF);
308 }
309 */
310
311 // Write a cooked byte
312
313 static
WriteByte(cmsIOHANDLER * m,cmsUInt8Number b)314 void WriteByte(cmsIOHANDLER* m, cmsUInt8Number b)
315 {
316 _cmsIOPrintf(m, "%02x", b);
317 _cmsPSActualColumn += 2;
318
319 if (_cmsPSActualColumn > MAXPSCOLS) {
320
321 _cmsIOPrintf(m, "\n");
322 _cmsPSActualColumn = 0;
323 }
324 }
325
326 // ----------------------------------------------------------------- PostScript generation
327
328
329 // Removes offending Carriage returns
330 static
RemoveCR(const char * txt)331 char* RemoveCR(const char* txt)
332 {
333 static char Buffer[2048];
334 char* pt;
335
336 strncpy(Buffer, txt, 2047);
337 Buffer[2047] = 0;
338 for (pt = Buffer; *pt; pt++)
339 if (*pt == '\n' || *pt == '\r') *pt = ' ';
340
341 return Buffer;
342
343 }
344
345 static
EmitHeader(cmsIOHANDLER * m,const char * Title,cmsHPROFILE hProfile)346 void EmitHeader(cmsIOHANDLER* m, const char* Title, cmsHPROFILE hProfile)
347 {
348 time_t timer;
349 cmsMLU *Description, *Copyright;
350 char DescASCII[256], CopyrightASCII[256];
351
352 time(&timer);
353
354 Description = (cmsMLU*) cmsReadTag(hProfile, cmsSigProfileDescriptionTag);
355 Copyright = (cmsMLU*) cmsReadTag(hProfile, cmsSigCopyrightTag);
356
357 DescASCII[0] = DescASCII[255] = 0;
358 CopyrightASCII[0] = CopyrightASCII[255] = 0;
359
360 if (Description != NULL) cmsMLUgetASCII(Description, cmsNoLanguage, cmsNoCountry, DescASCII, 255);
361 if (Copyright != NULL) cmsMLUgetASCII(Copyright, cmsNoLanguage, cmsNoCountry, CopyrightASCII, 255);
362
363 _cmsIOPrintf(m, "%%!PS-Adobe-3.0\n");
364 _cmsIOPrintf(m, "%%\n");
365 _cmsIOPrintf(m, "%% %s\n", Title);
366 _cmsIOPrintf(m, "%% Source: %s\n", RemoveCR(DescASCII));
367 _cmsIOPrintf(m, "%% %s\n", RemoveCR(CopyrightASCII));
368 _cmsIOPrintf(m, "%% Created: %s", ctime(&timer)); // ctime appends a \n!!!
369 _cmsIOPrintf(m, "%%\n");
370 _cmsIOPrintf(m, "%%%%BeginResource\n");
371
372 }
373
374
375 // Emits White & Black point. White point is always D50, Black point is the device
376 // Black point adapted to D50.
377
378 static
EmitWhiteBlackD50(cmsIOHANDLER * m,cmsCIEXYZ * BlackPoint)379 void EmitWhiteBlackD50(cmsIOHANDLER* m, cmsCIEXYZ* BlackPoint)
380 {
381
382 _cmsIOPrintf(m, "/BlackPoint [%f %f %f]\n", BlackPoint -> X,
383 BlackPoint -> Y,
384 BlackPoint -> Z);
385
386 _cmsIOPrintf(m, "/WhitePoint [%f %f %f]\n", cmsD50_XYZ()->X,
387 cmsD50_XYZ()->Y,
388 cmsD50_XYZ()->Z);
389 }
390
391
392 static
EmitRangeCheck(cmsIOHANDLER * m)393 void EmitRangeCheck(cmsIOHANDLER* m)
394 {
395 _cmsIOPrintf(m, "dup 0.0 lt { pop 0.0 } if "
396 "dup 1.0 gt { pop 1.0 } if ");
397
398 }
399
400 // Does write the intent
401
402 static
EmitIntent(cmsIOHANDLER * m,int RenderingIntent)403 void EmitIntent(cmsIOHANDLER* m, int RenderingIntent)
404 {
405 const char *intent;
406
407 switch (RenderingIntent) {
408
409 case INTENT_PERCEPTUAL: intent = "Perceptual"; break;
410 case INTENT_RELATIVE_COLORIMETRIC: intent = "RelativeColorimetric"; break;
411 case INTENT_ABSOLUTE_COLORIMETRIC: intent = "AbsoluteColorimetric"; break;
412 case INTENT_SATURATION: intent = "Saturation"; break;
413
414 default: intent = "Undefined"; break;
415 }
416
417 _cmsIOPrintf(m, "/RenderingIntent (%s)\n", intent );
418 }
419
420 //
421 // Convert L* to Y
422 //
423 // Y = Yn*[ (L* + 16) / 116] ^ 3 if (L*) >= 6 / 29
424 // = Yn*( L* / 116) / 7.787 if (L*) < 6 / 29
425 //
426
427 /*
428 static
429 void EmitL2Y(cmsIOHANDLER* m)
430 {
431 _cmsIOPrintf(m,
432 "{ "
433 "100 mul 16 add 116 div " // (L * 100 + 16) / 116
434 "dup 6 29 div ge " // >= 6 / 29 ?
435 "{ dup dup mul mul } " // yes, ^3 and done
436 "{ 4 29 div sub 108 841 div mul } " // no, slope limiting
437 "ifelse } bind ");
438 }
439 */
440
441
442 // Lab -> XYZ, see the discussion above
443
444 static
EmitLab2XYZ(cmsIOHANDLER * m)445 void EmitLab2XYZ(cmsIOHANDLER* m)
446 {
447 _cmsIOPrintf(m, "/RangeABC [ 0 1 0 1 0 1]\n");
448 _cmsIOPrintf(m, "/DecodeABC [\n");
449 _cmsIOPrintf(m, "{100 mul 16 add 116 div } bind\n");
450 _cmsIOPrintf(m, "{255 mul 128 sub 500 div } bind\n");
451 _cmsIOPrintf(m, "{255 mul 128 sub 200 div } bind\n");
452 _cmsIOPrintf(m, "]\n");
453 _cmsIOPrintf(m, "/MatrixABC [ 1 1 1 1 0 0 0 0 -1]\n");
454 _cmsIOPrintf(m, "/RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]\n");
455 _cmsIOPrintf(m, "/DecodeLMN [\n");
456 _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");
457 _cmsIOPrintf(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind\n");
458 _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");
459 _cmsIOPrintf(m, "]\n");
460 }
461
462
463
464 // Outputs a table of words. It does use 16 bits
465
466 static
Emit1Gamma(cmsIOHANDLER * m,cmsToneCurve * Table)467 void Emit1Gamma(cmsIOHANDLER* m, cmsToneCurve* Table)
468 {
469 cmsUInt32Number i;
470 cmsFloat64Number gamma;
471
472 if (Table == NULL) return; // Error
473
474 if (Table ->nEntries <= 0) return; // Empty table
475
476 // Suppress whole if identity
477 if (cmsIsToneCurveLinear(Table)) return;
478
479 // Check if is really an exponential. If so, emit "exp"
480 gamma = cmsEstimateGamma(Table, 0.001);
481 if (gamma > 0) {
482 _cmsIOPrintf(m, "{ %g exp } bind ", gamma);
483 return;
484 }
485
486 _cmsIOPrintf(m, "{ ");
487
488 // Bounds check
489 EmitRangeCheck(m);
490
491 // Emit intepolation code
492
493 // PostScript code Stack
494 // =============== ========================
495 // v
496 _cmsIOPrintf(m, " [");
497
498 for (i=0; i < Table->nEntries; i++) {
499 _cmsIOPrintf(m, "%d ", Table->Table16[i]);
500 }
501
502 _cmsIOPrintf(m, "] "); // v tab
503
504 _cmsIOPrintf(m, "dup "); // v tab tab
505 _cmsIOPrintf(m, "length 1 sub "); // v tab dom
506 _cmsIOPrintf(m, "3 -1 roll "); // tab dom v
507 _cmsIOPrintf(m, "mul "); // tab val2
508 _cmsIOPrintf(m, "dup "); // tab val2 val2
509 _cmsIOPrintf(m, "dup "); // tab val2 val2 val2
510 _cmsIOPrintf(m, "floor cvi "); // tab val2 val2 cell0
511 _cmsIOPrintf(m, "exch "); // tab val2 cell0 val2
512 _cmsIOPrintf(m, "ceiling cvi "); // tab val2 cell0 cell1
513 _cmsIOPrintf(m, "3 index "); // tab val2 cell0 cell1 tab
514 _cmsIOPrintf(m, "exch "); // tab val2 cell0 tab cell1
515 _cmsIOPrintf(m, "get "); // tab val2 cell0 y1
516 _cmsIOPrintf(m, "4 -1 roll "); // val2 cell0 y1 tab
517 _cmsIOPrintf(m, "3 -1 roll "); // val2 y1 tab cell0
518 _cmsIOPrintf(m, "get "); // val2 y1 y0
519 _cmsIOPrintf(m, "dup "); // val2 y1 y0 y0
520 _cmsIOPrintf(m, "3 1 roll "); // val2 y0 y1 y0
521 _cmsIOPrintf(m, "sub "); // val2 y0 (y1-y0)
522 _cmsIOPrintf(m, "3 -1 roll "); // y0 (y1-y0) val2
523 _cmsIOPrintf(m, "dup "); // y0 (y1-y0) val2 val2
524 _cmsIOPrintf(m, "floor cvi "); // y0 (y1-y0) val2 floor(val2)
525 _cmsIOPrintf(m, "sub "); // y0 (y1-y0) rest
526 _cmsIOPrintf(m, "mul "); // y0 t1
527 _cmsIOPrintf(m, "add "); // y
528 _cmsIOPrintf(m, "65535 div "); // result
529
530 _cmsIOPrintf(m, " } bind ");
531 }
532
533
534 // Compare gamma table
535
536 static
GammaTableEquals(cmsUInt16Number * g1,cmsUInt16Number * g2,int nEntries)537 cmsBool GammaTableEquals(cmsUInt16Number* g1, cmsUInt16Number* g2, int nEntries)
538 {
539 return memcmp(g1, g2, nEntries* sizeof(cmsUInt16Number)) == 0;
540 }
541
542
543 // Does write a set of gamma curves
544
545 static
EmitNGamma(cmsIOHANDLER * m,int n,cmsToneCurve * g[])546 void EmitNGamma(cmsIOHANDLER* m, int n, cmsToneCurve* g[])
547 {
548 int i;
549
550 for( i=0; i < n; i++ )
551 {
552 if (g[i] == NULL) return; // Error
553
554 if (i > 0 && GammaTableEquals(g[i-1]->Table16, g[i]->Table16, g[i]->nEntries)) {
555
556 _cmsIOPrintf(m, "dup ");
557 }
558 else {
559 Emit1Gamma(m, g[i]);
560 }
561 }
562
563 }
564
565
566
567
568
569 // Following code dumps a LUT onto memory stream
570
571
572 // This is the sampler. Intended to work in SAMPLER_INSPECT mode,
573 // that is, the callback will be called for each knot with
574 //
575 // In[] The grid location coordinates, normalized to 0..ffff
576 // Out[] The Pipeline values, normalized to 0..ffff
577 //
578 // Returning a value other than 0 does terminate the sampling process
579 //
580 // Each row contains Pipeline values for all but first component. So, I
581 // detect row changing by keeping a copy of last value of first
582 // component. -1 is used to mark beginning of whole block.
583
584 static
OutputValueSampler(register const cmsUInt16Number In[],register cmsUInt16Number Out[],register void * Cargo)585 int OutputValueSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
586 {
587 cmsPsSamplerCargo* sc = (cmsPsSamplerCargo*) Cargo;
588 cmsUInt32Number i;
589
590
591 if (sc -> FixWhite) {
592
593 if (In[0] == 0xFFFF) { // Only in L* = 100, ab = [-8..8]
594
595 if ((In[1] >= 0x7800 && In[1] <= 0x8800) &&
596 (In[2] >= 0x7800 && In[2] <= 0x8800)) {
597
598 cmsUInt16Number* Black;
599 cmsUInt16Number* White;
600 cmsUInt32Number nOutputs;
601
602 if (!_cmsEndPointsBySpace(sc ->ColorSpace, &White, &Black, &nOutputs))
603 return 0;
604
605 for (i=0; i < nOutputs; i++)
606 Out[i] = White[i];
607 }
608
609
610 }
611 }
612
613
614 // Hadle the parenthesis on rows
615
616 if (In[0] != sc ->FirstComponent) {
617
618 if (sc ->FirstComponent != -1) {
619
620 _cmsIOPrintf(sc ->m, sc ->PostMin);
621 sc ->SecondComponent = -1;
622 _cmsIOPrintf(sc ->m, sc ->PostMaj);
623 }
624
625 // Begin block
626 _cmsPSActualColumn = 0;
627
628 _cmsIOPrintf(sc ->m, sc ->PreMaj);
629 sc ->FirstComponent = In[0];
630 }
631
632
633 if (In[1] != sc ->SecondComponent) {
634
635 if (sc ->SecondComponent != -1) {
636
637 _cmsIOPrintf(sc ->m, sc ->PostMin);
638 }
639
640 _cmsIOPrintf(sc ->m, sc ->PreMin);
641 sc ->SecondComponent = In[1];
642 }
643
644 // Dump table.
645
646 for (i=0; i < sc -> Pipeline ->Params->nOutputs; i++) {
647
648 cmsUInt16Number wWordOut = Out[i];
649 cmsUInt8Number wByteOut; // Value as byte
650
651
652 // We always deal with Lab4
653
654 wByteOut = Word2Byte(wWordOut);
655 WriteByte(sc -> m, wByteOut);
656 }
657
658 return 1;
659 }
660
661 // Writes a Pipeline on memstream. Could be 8 or 16 bits based
662
663 static
WriteCLUT(cmsIOHANDLER * m,cmsStage * mpe,const char * PreMaj,const char * PostMaj,const char * PreMin,const char * PostMin,int FixWhite,cmsColorSpaceSignature ColorSpace)664 void WriteCLUT(cmsIOHANDLER* m, cmsStage* mpe, const char* PreMaj,
665 const char* PostMaj,
666 const char* PreMin,
667 const char* PostMin,
668 int FixWhite,
669 cmsColorSpaceSignature ColorSpace)
670 {
671 cmsUInt32Number i;
672 cmsPsSamplerCargo sc;
673
674 sc.FirstComponent = -1;
675 sc.SecondComponent = -1;
676 sc.Pipeline = (_cmsStageCLutData *) mpe ->Data;
677 sc.m = m;
678 sc.PreMaj = PreMaj;
679 sc.PostMaj= PostMaj;
680
681 sc.PreMin = PreMin;
682 sc.PostMin = PostMin;
683 sc.FixWhite = FixWhite;
684 sc.ColorSpace = ColorSpace;
685
686 _cmsIOPrintf(m, "[");
687
688 for (i=0; i < sc.Pipeline->Params->nInputs; i++)
689 _cmsIOPrintf(m, " %d ", sc.Pipeline->Params->nSamples[i]);
690
691 _cmsIOPrintf(m, " [\n");
692
693 cmsStageSampleCLut16bit(mpe, OutputValueSampler, (void*) &sc, SAMPLER_INSPECT);
694
695 _cmsIOPrintf(m, PostMin);
696 _cmsIOPrintf(m, PostMaj);
697 _cmsIOPrintf(m, "] ");
698
699 }
700
701
702 // Dumps CIEBasedA Color Space Array
703
704 static
EmitCIEBasedA(cmsIOHANDLER * m,cmsToneCurve * Curve,cmsCIEXYZ * BlackPoint)705 int EmitCIEBasedA(cmsIOHANDLER* m, cmsToneCurve* Curve, cmsCIEXYZ* BlackPoint)
706 {
707
708 _cmsIOPrintf(m, "[ /CIEBasedA\n");
709 _cmsIOPrintf(m, " <<\n");
710
711 _cmsIOPrintf(m, "/DecodeA ");
712
713 Emit1Gamma(m, Curve);
714
715 _cmsIOPrintf(m, " \n");
716
717 _cmsIOPrintf(m, "/MatrixA [ 0.9642 1.0000 0.8249 ]\n");
718 _cmsIOPrintf(m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
719
720 EmitWhiteBlackD50(m, BlackPoint);
721 EmitIntent(m, INTENT_PERCEPTUAL);
722
723 _cmsIOPrintf(m, ">>\n");
724 _cmsIOPrintf(m, "]\n");
725
726 return 1;
727 }
728
729
730 // Dumps CIEBasedABC Color Space Array
731
732 static
EmitCIEBasedABC(cmsIOHANDLER * m,cmsFloat64Number * Matrix,cmsToneCurve ** CurveSet,cmsCIEXYZ * BlackPoint)733 int EmitCIEBasedABC(cmsIOHANDLER* m, cmsFloat64Number* Matrix, cmsToneCurve** CurveSet, cmsCIEXYZ* BlackPoint)
734 {
735 int i;
736
737 _cmsIOPrintf(m, "[ /CIEBasedABC\n");
738 _cmsIOPrintf(m, "<<\n");
739 _cmsIOPrintf(m, "/DecodeABC [ ");
740
741 EmitNGamma(m, 3, CurveSet);
742
743 _cmsIOPrintf(m, "]\n");
744
745 _cmsIOPrintf(m, "/MatrixABC [ " );
746
747 for( i=0; i < 3; i++ ) {
748
749 _cmsIOPrintf(m, "%.6f %.6f %.6f ", Matrix[i + 3*0],
750 Matrix[i + 3*1],
751 Matrix[i + 3*2]);
752 }
753
754
755 _cmsIOPrintf(m, "]\n");
756
757 _cmsIOPrintf(m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
758
759 EmitWhiteBlackD50(m, BlackPoint);
760 EmitIntent(m, INTENT_PERCEPTUAL);
761
762 _cmsIOPrintf(m, ">>\n");
763 _cmsIOPrintf(m, "]\n");
764
765
766 return 1;
767 }
768
769
770 static
EmitCIEBasedDEF(cmsIOHANDLER * m,cmsPipeline * Pipeline,int Intent,cmsCIEXYZ * BlackPoint)771 int EmitCIEBasedDEF(cmsIOHANDLER* m, cmsPipeline* Pipeline, int Intent, cmsCIEXYZ* BlackPoint)
772 {
773 const char* PreMaj;
774 const char* PostMaj;
775 const char* PreMin, *PostMin;
776 cmsStage* mpe;
777
778 mpe = Pipeline ->Elements;
779
780 switch (cmsStageInputChannels(mpe)) {
781 case 3:
782
783 _cmsIOPrintf(m, "[ /CIEBasedDEF\n");
784 PreMaj ="<";
785 PostMaj= ">\n";
786 PreMin = PostMin = "";
787 break;
788 case 4:
789 _cmsIOPrintf(m, "[ /CIEBasedDEFG\n");
790 PreMaj = "[";
791 PostMaj = "]\n";
792 PreMin = "<";
793 PostMin = ">\n";
794 break;
795 default:
796 return 0;
797
798 }
799
800 _cmsIOPrintf(m, "<<\n");
801
802 if (cmsStageType(mpe) == cmsSigCurveSetElemType) {
803
804 _cmsIOPrintf(m, "/DecodeDEF [ ");
805 EmitNGamma(m, cmsStageOutputChannels(mpe), _cmsStageGetPtrToCurveSet(mpe));
806 _cmsIOPrintf(m, "]\n");
807
808 mpe = mpe ->Next;
809 }
810
811 if (cmsStageType(mpe) == cmsSigCLutElemType) {
812
813 _cmsIOPrintf(m, "/Table ");
814 WriteCLUT(m, mpe, PreMaj, PostMaj, PreMin, PostMin, FALSE, (cmsColorSpaceSignature) 0);
815 _cmsIOPrintf(m, "]\n");
816 }
817
818 EmitLab2XYZ(m);
819 EmitWhiteBlackD50(m, BlackPoint);
820 EmitIntent(m, Intent);
821
822 _cmsIOPrintf(m, " >>\n");
823 _cmsIOPrintf(m, "]\n");
824
825 return 1;
826 }
827
828 // Generates a curve from a gray profile
829
830 static
ExtractGray2Y(cmsContext ContextID,cmsHPROFILE hProfile,int Intent)831 cmsToneCurve* ExtractGray2Y(cmsContext ContextID, cmsHPROFILE hProfile, int Intent)
832 {
833 cmsToneCurve* Out = cmsBuildTabulatedToneCurve16(ContextID, 256, NULL);
834 cmsHPROFILE hXYZ = cmsCreateXYZProfile();
835 cmsHTRANSFORM xform = cmsCreateTransformTHR(ContextID, hProfile, TYPE_GRAY_8, hXYZ, TYPE_XYZ_DBL, Intent, cmsFLAGS_NOOPTIMIZE);
836 int i;
837
838 if (Out != NULL) {
839 for (i=0; i < 256; i++) {
840
841 cmsUInt8Number Gray = (cmsUInt8Number) i;
842 cmsCIEXYZ XYZ;
843
844 cmsDoTransform(xform, &Gray, &XYZ, 1);
845
846 Out ->Table16[i] =_cmsQuickSaturateWord(XYZ.Y * 65535.0);
847 }
848 }
849
850 cmsDeleteTransform(xform);
851 cmsCloseProfile(hXYZ);
852 return Out;
853 }
854
855
856
857 // Because PostScript has only 8 bits in /Table, we should use
858 // a more perceptually uniform space... I do choose Lab.
859
860 static
WriteInputLUT(cmsIOHANDLER * m,cmsHPROFILE hProfile,int Intent,cmsUInt32Number dwFlags)861 int WriteInputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, int Intent, cmsUInt32Number dwFlags)
862 {
863 cmsHPROFILE hLab;
864 cmsHTRANSFORM xform;
865 cmsUInt32Number nChannels;
866 cmsUInt32Number InputFormat;
867 int rc;
868 cmsHPROFILE Profiles[2];
869 cmsCIEXYZ BlackPointAdaptedToD50;
870
871 // Does create a device-link based transform.
872 // The DeviceLink is next dumped as working CSA.
873
874 InputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE);
875 nChannels = T_CHANNELS(InputFormat);
876
877
878 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0);
879
880 // Adjust output to Lab4
881 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
882
883 Profiles[0] = hProfile;
884 Profiles[1] = hLab;
885
886 xform = cmsCreateMultiprofileTransform(Profiles, 2, InputFormat, TYPE_Lab_DBL, Intent, 0);
887 cmsCloseProfile(hLab);
888
889 if (xform == NULL) {
890
891 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Profile -> Lab");
892 return 0;
893 }
894
895 // Only 1, 3 and 4 channels are allowed
896
897 switch (nChannels) {
898
899 case 1: {
900 cmsToneCurve* Gray2Y = ExtractGray2Y(m ->ContextID, hProfile, Intent);
901 EmitCIEBasedA(m, Gray2Y, &BlackPointAdaptedToD50);
902 cmsFreeToneCurve(Gray2Y);
903 }
904 break;
905
906 case 3:
907 case 4: {
908 cmsUInt32Number OutFrm = TYPE_Lab_16;
909 cmsPipeline* DeviceLink;
910 _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform;
911
912 DeviceLink = cmsPipelineDup(v ->Lut);
913 if (DeviceLink == NULL) return 0;
914
915 dwFlags |= cmsFLAGS_FORCE_CLUT;
916 _cmsOptimizePipeline(m->ContextID, &DeviceLink, Intent, &InputFormat, &OutFrm, &dwFlags);
917
918 rc = EmitCIEBasedDEF(m, DeviceLink, Intent, &BlackPointAdaptedToD50);
919 cmsPipelineFree(DeviceLink);
920 if (rc == 0) return 0;
921 }
922 break;
923
924 default:
925
926 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Only 3, 4 channels supported for CSA. This profile has %d channels.", nChannels);
927 return 0;
928 }
929
930
931 cmsDeleteTransform(xform);
932
933 return 1;
934 }
935
936 static
GetPtrToMatrix(const cmsStage * mpe)937 cmsFloat64Number* GetPtrToMatrix(const cmsStage* mpe)
938 {
939 _cmsStageMatrixData* Data = (_cmsStageMatrixData*) mpe ->Data;
940
941 return Data -> Double;
942 }
943
944
945 // Does create CSA based on matrix-shaper. Allowed types are gray and RGB based
946
947 static
WriteInputMatrixShaper(cmsIOHANDLER * m,cmsHPROFILE hProfile,cmsStage * Matrix,cmsStage * Shaper)948 int WriteInputMatrixShaper(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsStage* Matrix, cmsStage* Shaper)
949 {
950 cmsColorSpaceSignature ColorSpace;
951 int rc;
952 cmsCIEXYZ BlackPointAdaptedToD50;
953
954 ColorSpace = cmsGetColorSpace(hProfile);
955
956 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0);
957
958 if (ColorSpace == cmsSigGrayData) {
959
960 cmsToneCurve** ShaperCurve = _cmsStageGetPtrToCurveSet(Shaper);
961 rc = EmitCIEBasedA(m, ShaperCurve[0], &BlackPointAdaptedToD50);
962
963 }
964 else
965 if (ColorSpace == cmsSigRgbData) {
966
967 cmsMAT3 Mat;
968 int i, j;
969
970 memmove(&Mat, GetPtrToMatrix(Matrix), sizeof(Mat));
971
972 for (i=0; i < 3; i++)
973 for (j=0; j < 3; j++)
974 Mat.v[i].n[j] *= MAX_ENCODEABLE_XYZ;
975
976 rc = EmitCIEBasedABC(m, (cmsFloat64Number *) &Mat,
977 _cmsStageGetPtrToCurveSet(Shaper),
978 &BlackPointAdaptedToD50);
979 }
980 else {
981
982 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Profile is not suitable for CSA. Unsupported colorspace.");
983 return 0;
984 }
985
986 return rc;
987 }
988
989
990
991 // Creates a PostScript color list from a named profile data.
992 // This is a HP extension, and it works in Lab instead of XYZ
993
994 static
WriteNamedColorCSA(cmsIOHANDLER * m,cmsHPROFILE hNamedColor,int Intent)995 int WriteNamedColorCSA(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, int Intent)
996 {
997 cmsHTRANSFORM xform;
998 cmsHPROFILE hLab;
999 int i, nColors;
1000 char ColorName[32];
1001 cmsNAMEDCOLORLIST* NamedColorList;
1002
1003 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
1004 xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, hLab, TYPE_Lab_DBL, Intent, 0);
1005 if (xform == NULL) return 0;
1006
1007 NamedColorList = cmsGetNamedColorList(xform);
1008 if (NamedColorList == NULL) return 0;
1009
1010 _cmsIOPrintf(m, "<<\n");
1011 _cmsIOPrintf(m, "(colorlistcomment) (%s)\n", "Named color CSA");
1012 _cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
1013 _cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");
1014
1015 nColors = cmsNamedColorCount(NamedColorList);
1016
1017
1018 for (i=0; i < nColors; i++) {
1019
1020 cmsUInt16Number In[1];
1021 cmsCIELab Lab;
1022
1023 In[0] = (cmsUInt16Number) i;
1024
1025 if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL))
1026 continue;
1027
1028 cmsDoTransform(xform, In, &Lab, 1);
1029 _cmsIOPrintf(m, " (%s) [ %.3f %.3f %.3f ]\n", ColorName, Lab.L, Lab.a, Lab.b);
1030 }
1031
1032
1033
1034 _cmsIOPrintf(m, ">>\n");
1035
1036 cmsDeleteTransform(xform);
1037 cmsCloseProfile(hLab);
1038 return 1;
1039 }
1040
1041
1042 // Does create a Color Space Array on XYZ colorspace for PostScript usage
1043 static
GenerateCSA(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,cmsIOHANDLER * mem)1044 cmsUInt32Number GenerateCSA(cmsContext ContextID,
1045 cmsHPROFILE hProfile,
1046 cmsUInt32Number Intent,
1047 cmsUInt32Number dwFlags,
1048 cmsIOHANDLER* mem)
1049 {
1050 cmsUInt32Number dwBytesUsed;
1051 cmsPipeline* lut = NULL;
1052 cmsStage* Matrix, *Shaper;
1053
1054
1055 // Is a named color profile?
1056 if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
1057
1058 if (!WriteNamedColorCSA(mem, hProfile, Intent)) goto Error;
1059 }
1060 else {
1061
1062
1063 // Any profile class are allowed (including devicelink), but
1064 // output (PCS) colorspace must be XYZ or Lab
1065 cmsColorSpaceSignature ColorSpace = cmsGetPCS(hProfile);
1066
1067 if (ColorSpace != cmsSigXYZData &&
1068 ColorSpace != cmsSigLabData) {
1069
1070 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Invalid output color space");
1071 goto Error;
1072 }
1073
1074
1075 // Read the lut with all necessary conversion stages
1076 lut = _cmsReadInputLUT(hProfile, Intent);
1077 if (lut == NULL) goto Error;
1078
1079
1080 // Tone curves + matrix can be implemented without any LUT
1081 if (cmsPipelineCheckAndRetreiveStages(lut, 2, cmsSigCurveSetElemType, cmsSigMatrixElemType, &Shaper, &Matrix)) {
1082
1083 if (!WriteInputMatrixShaper(mem, hProfile, Matrix, Shaper)) goto Error;
1084
1085 }
1086 else {
1087 // We need a LUT for the rest
1088 if (!WriteInputLUT(mem, hProfile, Intent, dwFlags)) goto Error;
1089 }
1090 }
1091
1092
1093 // Done, keep memory usage
1094 dwBytesUsed = mem ->UsedSpace;
1095
1096 // Get rid of LUT
1097 if (lut != NULL) cmsPipelineFree(lut);
1098
1099 // Finally, return used byte count
1100 return dwBytesUsed;
1101
1102 Error:
1103 if (lut != NULL) cmsPipelineFree(lut);
1104 return 0;
1105 }
1106
1107 // ------------------------------------------------------ Color Rendering Dictionary (CRD)
1108
1109
1110
1111 /*
1112
1113 Black point compensation plus chromatic adaptation:
1114
1115 Step 1 - Chromatic adaptation
1116 =============================
1117
1118 WPout
1119 X = ------- PQR
1120 Wpin
1121
1122 Step 2 - Black point compensation
1123 =================================
1124
1125 (WPout - BPout)*X - WPout*(BPin - BPout)
1126 out = ---------------------------------------
1127 WPout - BPin
1128
1129
1130 Algorithm discussion
1131 ====================
1132
1133 TransformPQR(WPin, BPin, WPout, BPout, PQR)
1134
1135 Wpin,etc= { Xws Yws Zws Pws Qws Rws }
1136
1137
1138 Algorithm Stack 0...n
1139 ===========================================================
1140 PQR BPout WPout BPin WPin
1141 4 index 3 get WPin PQR BPout WPout BPin WPin
1142 div (PQR/WPin) BPout WPout BPin WPin
1143 2 index 3 get WPout (PQR/WPin) BPout WPout BPin WPin
1144 mult WPout*(PQR/WPin) BPout WPout BPin WPin
1145
1146 2 index 3 get WPout WPout*(PQR/WPin) BPout WPout BPin WPin
1147 2 index 3 get BPout WPout WPout*(PQR/WPin) BPout WPout BPin WPin
1148 sub (WPout-BPout) WPout*(PQR/WPin) BPout WPout BPin WPin
1149 mult (WPout-BPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1150
1151 2 index 3 get WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1152 4 index 3 get BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1153 3 index 3 get BPout BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1154
1155 sub (BPin-BPout) WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1156 mult (BPin-BPout)*WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1157 sub (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1158
1159 3 index 3 get BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1160 3 index 3 get WPout BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1161 exch
1162 sub (WPout-BPin) (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1163 div
1164
1165 exch pop
1166 exch pop
1167 exch pop
1168 exch pop
1169
1170 */
1171
1172
1173 static
EmitPQRStage(cmsIOHANDLER * m,cmsHPROFILE hProfile,int DoBPC,int lIsAbsolute)1174 void EmitPQRStage(cmsIOHANDLER* m, cmsHPROFILE hProfile, int DoBPC, int lIsAbsolute)
1175 {
1176
1177
1178 if (lIsAbsolute) {
1179
1180 // For absolute colorimetric intent, encode back to relative
1181 // and generate a relative Pipeline
1182
1183 // Relative encoding is obtained across XYZpcs*(D50/WhitePoint)
1184
1185 cmsCIEXYZ White;
1186
1187 _cmsReadMediaWhitePoint(&White, hProfile);
1188
1189 _cmsIOPrintf(m,"/MatrixPQR [1 0 0 0 1 0 0 0 1 ]\n");
1190 _cmsIOPrintf(m,"/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");
1191
1192 _cmsIOPrintf(m, "%% Absolute colorimetric -- encode to relative to maximize LUT usage\n"
1193 "/TransformPQR [\n"
1194 "{0.9642 mul %g div exch pop exch pop exch pop exch pop} bind\n"
1195 "{1.0000 mul %g div exch pop exch pop exch pop exch pop} bind\n"
1196 "{0.8249 mul %g div exch pop exch pop exch pop exch pop} bind\n]\n",
1197 White.X, White.Y, White.Z);
1198 return;
1199 }
1200
1201
1202 _cmsIOPrintf(m,"%% Bradford Cone Space\n"
1203 "/MatrixPQR [0.8951 -0.7502 0.0389 0.2664 1.7135 -0.0685 -0.1614 0.0367 1.0296 ] \n");
1204
1205 _cmsIOPrintf(m, "/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");
1206
1207
1208 // No BPC
1209
1210 if (!DoBPC) {
1211
1212 _cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space\n"
1213 "/TransformPQR [\n"
1214 "{exch pop exch 3 get mul exch pop exch 3 get div} bind\n"
1215 "{exch pop exch 4 get mul exch pop exch 4 get div} bind\n"
1216 "{exch pop exch 5 get mul exch pop exch 5 get div} bind\n]\n");
1217 } else {
1218
1219 // BPC
1220
1221 _cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space plus BPC\n"
1222 "/TransformPQR [\n");
1223
1224 _cmsIOPrintf(m, "{4 index 3 get div 2 index 3 get mul "
1225 "2 index 3 get 2 index 3 get sub mul "
1226 "2 index 3 get 4 index 3 get 3 index 3 get sub mul sub "
1227 "3 index 3 get 3 index 3 get exch sub div "
1228 "exch pop exch pop exch pop exch pop } bind\n");
1229
1230 _cmsIOPrintf(m, "{4 index 4 get div 2 index 4 get mul "
1231 "2 index 4 get 2 index 4 get sub mul "
1232 "2 index 4 get 4 index 4 get 3 index 4 get sub mul sub "
1233 "3 index 4 get 3 index 4 get exch sub div "
1234 "exch pop exch pop exch pop exch pop } bind\n");
1235
1236 _cmsIOPrintf(m, "{4 index 5 get div 2 index 5 get mul "
1237 "2 index 5 get 2 index 5 get sub mul "
1238 "2 index 5 get 4 index 5 get 3 index 5 get sub mul sub "
1239 "3 index 5 get 3 index 5 get exch sub div "
1240 "exch pop exch pop exch pop exch pop } bind\n]\n");
1241
1242 }
1243
1244
1245 }
1246
1247
1248 static
EmitXYZ2Lab(cmsIOHANDLER * m)1249 void EmitXYZ2Lab(cmsIOHANDLER* m)
1250 {
1251 _cmsIOPrintf(m, "/RangeLMN [ -0.635 2.0 0 2 -0.635 2.0 ]\n");
1252 _cmsIOPrintf(m, "/EncodeLMN [\n");
1253 _cmsIOPrintf(m, "{ 0.964200 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1254 _cmsIOPrintf(m, "{ 1.000000 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1255 _cmsIOPrintf(m, "{ 0.824900 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1256 _cmsIOPrintf(m, "]\n");
1257 _cmsIOPrintf(m, "/MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]\n");
1258 _cmsIOPrintf(m, "/EncodeABC [\n");
1259
1260
1261 _cmsIOPrintf(m, "{ 116 mul 16 sub 100 div } bind\n");
1262 _cmsIOPrintf(m, "{ 500 mul 128 add 256 div } bind\n");
1263 _cmsIOPrintf(m, "{ 200 mul 128 add 256 div } bind\n");
1264
1265
1266 _cmsIOPrintf(m, "]\n");
1267
1268
1269 }
1270
1271 // Due to impedance mismatch between XYZ and almost all RGB and CMYK spaces
1272 // I choose to dump LUTS in Lab instead of XYZ. There is still a lot of wasted
1273 // space on 3D CLUT, but since space seems not to be a problem here, 33 points
1274 // would give a reasonable accurancy. Note also that CRD tables must operate in
1275 // 8 bits.
1276
1277 static
WriteOutputLUT(cmsIOHANDLER * m,cmsHPROFILE hProfile,int Intent,cmsUInt32Number dwFlags)1278 int WriteOutputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, int Intent, cmsUInt32Number dwFlags)
1279 {
1280 cmsHPROFILE hLab;
1281 cmsHTRANSFORM xform;
1282 int i, nChannels;
1283 cmsUInt32Number OutputFormat;
1284 _cmsTRANSFORM* v;
1285 cmsPipeline* DeviceLink;
1286 cmsHPROFILE Profiles[3];
1287 cmsCIEXYZ BlackPointAdaptedToD50;
1288 cmsBool lDoBPC = (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION);
1289 cmsBool lFixWhite = !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP);
1290 cmsUInt32Number InFrm = TYPE_Lab_16;
1291 int RelativeEncodingIntent;
1292 cmsColorSpaceSignature ColorSpace;
1293
1294
1295 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
1296 if (hLab == NULL) return 0;
1297
1298 OutputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE);
1299 nChannels = T_CHANNELS(OutputFormat);
1300
1301 ColorSpace = cmsGetColorSpace(hProfile);
1302
1303 // For absolute colorimetric, the LUT is encoded as relative in order to preserve precision.
1304
1305 RelativeEncodingIntent = Intent;
1306 if (RelativeEncodingIntent == INTENT_ABSOLUTE_COLORIMETRIC)
1307 RelativeEncodingIntent = INTENT_RELATIVE_COLORIMETRIC;
1308
1309
1310 // Use V4 Lab always
1311 Profiles[0] = hLab;
1312 Profiles[1] = hProfile;
1313
1314 xform = cmsCreateMultiprofileTransformTHR(m ->ContextID,
1315 Profiles, 2, TYPE_Lab_DBL,
1316 OutputFormat, RelativeEncodingIntent, 0);
1317 cmsCloseProfile(hLab);
1318
1319 if (xform == NULL) {
1320
1321 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Lab -> Profile in CRD creation");
1322 return 0;
1323 }
1324
1325 // Get a copy of the internal devicelink
1326 v = (_cmsTRANSFORM*) xform;
1327 DeviceLink = cmsPipelineDup(v ->Lut);
1328 if (DeviceLink == NULL) return 0;
1329
1330
1331 // We need a CLUT
1332 dwFlags |= cmsFLAGS_FORCE_CLUT;
1333 _cmsOptimizePipeline(m->ContextID, &DeviceLink, RelativeEncodingIntent, &InFrm, &OutputFormat, &dwFlags);
1334
1335 _cmsIOPrintf(m, "<<\n");
1336 _cmsIOPrintf(m, "/ColorRenderingType 1\n");
1337
1338
1339 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0);
1340
1341 // Emit headers, etc.
1342 EmitWhiteBlackD50(m, &BlackPointAdaptedToD50);
1343 EmitPQRStage(m, hProfile, lDoBPC, Intent == INTENT_ABSOLUTE_COLORIMETRIC);
1344 EmitXYZ2Lab(m);
1345
1346
1347 // FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab
1348 // does map a=b=0 not falling into any specific node. Since range a,b goes -128..127,
1349 // zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to
1350 // zero. This would sacrifice a bit of highlights, but failure to do so would cause
1351 // scum dot. Ouch.
1352
1353 if (Intent == INTENT_ABSOLUTE_COLORIMETRIC)
1354 lFixWhite = FALSE;
1355
1356 _cmsIOPrintf(m, "/RenderTable ");
1357
1358
1359 WriteCLUT(m, cmsPipelineGetPtrToFirstStage(DeviceLink), "<", ">\n", "", "", lFixWhite, ColorSpace);
1360
1361 _cmsIOPrintf(m, " %d {} bind ", nChannels);
1362
1363 for (i=1; i < nChannels; i++)
1364 _cmsIOPrintf(m, "dup ");
1365
1366 _cmsIOPrintf(m, "]\n");
1367
1368
1369 EmitIntent(m, Intent);
1370
1371 _cmsIOPrintf(m, ">>\n");
1372
1373 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1374
1375 _cmsIOPrintf(m, "/Current exch /ColorRendering defineresource pop\n");
1376 }
1377
1378 cmsPipelineFree(DeviceLink);
1379 cmsDeleteTransform(xform);
1380
1381 return 1;
1382 }
1383
1384
1385 // Builds a ASCII string containing colorant list in 0..1.0 range
1386 static
BuildColorantList(char * Colorant,int nColorant,cmsUInt16Number Out[])1387 void BuildColorantList(char *Colorant, int nColorant, cmsUInt16Number Out[])
1388 {
1389 char Buff[32];
1390 int j;
1391
1392 Colorant[0] = 0;
1393 if (nColorant > cmsMAXCHANNELS)
1394 nColorant = cmsMAXCHANNELS;
1395
1396 for (j = 0; j < nColorant; j++) {
1397
1398 snprintf(Buff, 31, "%.3f", Out[j] / 65535.0);
1399 Buff[31] = 0;
1400 strcat(Colorant, Buff);
1401 if (j < nColorant - 1)
1402 strcat(Colorant, " ");
1403
1404 }
1405 }
1406
1407
1408 // Creates a PostScript color list from a named profile data.
1409 // This is a HP extension.
1410
1411 static
WriteNamedColorCRD(cmsIOHANDLER * m,cmsHPROFILE hNamedColor,int Intent,cmsUInt32Number dwFlags)1412 int WriteNamedColorCRD(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, int Intent, cmsUInt32Number dwFlags)
1413 {
1414 cmsHTRANSFORM xform;
1415 int i, nColors, nColorant;
1416 cmsUInt32Number OutputFormat;
1417 char ColorName[32];
1418 char Colorant[128];
1419 cmsNAMEDCOLORLIST* NamedColorList;
1420
1421
1422 OutputFormat = cmsFormatterForColorspaceOfProfile(hNamedColor, 2, FALSE);
1423 nColorant = T_CHANNELS(OutputFormat);
1424
1425
1426 xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, NULL, OutputFormat, Intent, dwFlags);
1427 if (xform == NULL) return 0;
1428
1429
1430 NamedColorList = cmsGetNamedColorList(xform);
1431 if (NamedColorList == NULL) return 0;
1432
1433 _cmsIOPrintf(m, "<<\n");
1434 _cmsIOPrintf(m, "(colorlistcomment) (%s) \n", "Named profile");
1435 _cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
1436 _cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");
1437
1438 nColors = cmsNamedColorCount(NamedColorList);
1439
1440 for (i=0; i < nColors; i++) {
1441
1442 cmsUInt16Number In[1];
1443 cmsUInt16Number Out[cmsMAXCHANNELS];
1444
1445 In[0] = (cmsUInt16Number) i;
1446
1447 if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL))
1448 continue;
1449
1450 cmsDoTransform(xform, In, Out, 1);
1451 BuildColorantList(Colorant, nColorant, Out);
1452 _cmsIOPrintf(m, " (%s) [ %s ]\n", ColorName, Colorant);
1453 }
1454
1455 _cmsIOPrintf(m, " >>");
1456
1457 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1458
1459 _cmsIOPrintf(m, " /Current exch /HPSpotTable defineresource pop\n");
1460 }
1461
1462 cmsDeleteTransform(xform);
1463 return 1;
1464 }
1465
1466
1467
1468 // This one does create a Color Rendering Dictionary.
1469 // CRD are always LUT-Based, no matter if profile is
1470 // implemented as matrix-shaper.
1471
1472 static
GenerateCRD(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,cmsIOHANDLER * mem)1473 cmsUInt32Number GenerateCRD(cmsContext ContextID,
1474 cmsHPROFILE hProfile,
1475 cmsUInt32Number Intent, cmsUInt32Number dwFlags,
1476 cmsIOHANDLER* mem)
1477 {
1478 cmsUInt32Number dwBytesUsed;
1479
1480 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1481
1482 EmitHeader(mem, "Color Rendering Dictionary (CRD)", hProfile);
1483 }
1484
1485
1486 // Is a named color profile?
1487 if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
1488
1489 if (!WriteNamedColorCRD(mem, hProfile, Intent, dwFlags)) {
1490 return 0;
1491 }
1492 }
1493 else {
1494
1495 // CRD are always implemented as LUT
1496
1497 if (!WriteOutputLUT(mem, hProfile, Intent, dwFlags)) {
1498 return 0;
1499 }
1500 }
1501
1502 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1503
1504 _cmsIOPrintf(mem, "%%%%EndResource\n");
1505 _cmsIOPrintf(mem, "\n%% CRD End\n");
1506 }
1507
1508 // Done, keep memory usage
1509 dwBytesUsed = mem ->UsedSpace;
1510
1511 // Finally, return used byte count
1512 return dwBytesUsed;
1513
1514 cmsUNUSED_PARAMETER(ContextID);
1515 }
1516
1517
1518
1519
cmsGetPostScriptColorResource(cmsContext ContextID,cmsPSResourceType Type,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,cmsIOHANDLER * io)1520 cmsUInt32Number CMSEXPORT cmsGetPostScriptColorResource(cmsContext ContextID,
1521 cmsPSResourceType Type,
1522 cmsHPROFILE hProfile,
1523 cmsUInt32Number Intent,
1524 cmsUInt32Number dwFlags,
1525 cmsIOHANDLER* io)
1526 {
1527 cmsUInt32Number rc;
1528
1529
1530 switch (Type) {
1531
1532 case cmsPS_RESOURCE_CSA:
1533 rc = GenerateCSA(ContextID, hProfile, Intent, dwFlags, io);
1534 break;
1535
1536 default:
1537 case cmsPS_RESOURCE_CRD:
1538 rc = GenerateCRD(ContextID, hProfile, Intent, dwFlags, io);
1539 break;
1540 }
1541
1542 return rc;
1543 }
1544
1545
1546
cmsGetPostScriptCRD(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,void * Buffer,cmsUInt32Number dwBufferLen)1547 cmsUInt32Number CMSEXPORT cmsGetPostScriptCRD(cmsContext ContextID,
1548 cmsHPROFILE hProfile,
1549 cmsUInt32Number Intent, cmsUInt32Number dwFlags,
1550 void* Buffer, cmsUInt32Number dwBufferLen)
1551 {
1552 cmsIOHANDLER* mem;
1553 cmsUInt32Number dwBytesUsed;
1554
1555 // Set up the serialization engine
1556 if (Buffer == NULL)
1557 mem = cmsOpenIOhandlerFromNULL(ContextID);
1558 else
1559 mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w");
1560
1561 if (!mem) return 0;
1562
1563 dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CRD, hProfile, Intent, dwFlags, mem);
1564
1565 // Get rid of memory stream
1566 cmsCloseIOhandler(mem);
1567
1568 return dwBytesUsed;
1569 }
1570
1571
1572
1573 // 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)1574 cmsUInt32Number CMSEXPORT cmsGetPostScriptCSA(cmsContext ContextID,
1575 cmsHPROFILE hProfile,
1576 cmsUInt32Number Intent,
1577 cmsUInt32Number dwFlags,
1578 void* Buffer,
1579 cmsUInt32Number dwBufferLen)
1580 {
1581 cmsIOHANDLER* mem;
1582 cmsUInt32Number dwBytesUsed;
1583
1584 if (Buffer == NULL)
1585 mem = cmsOpenIOhandlerFromNULL(ContextID);
1586 else
1587 mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w");
1588
1589 if (!mem) return 0;
1590
1591 dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CSA, hProfile, Intent, dwFlags, mem);
1592
1593 // Get rid of memory stream
1594 cmsCloseIOhandler(mem);
1595
1596 return dwBytesUsed;
1597
1598 }
1599