1 //---------------------------------------------------------------------------------
2 //
3 //  Little Color Management System
4 //  Copyright (c) 1998-2017 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 // 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(cmsContext ContextID,cmsIOHANDLER * m,cmsUInt8Number b)314 void WriteByte(cmsContext ContextID, cmsIOHANDLER* m, cmsUInt8Number b)
315 {
316     _cmsIOPrintf(ContextID, m, "%02x", b);
317     _cmsPSActualColumn += 2;
318 
319     if (_cmsPSActualColumn > MAXPSCOLS) {
320 
321         _cmsIOPrintf(ContextID, 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(cmsContext ContextID,cmsIOHANDLER * m,const char * Title,cmsHPROFILE hProfile)346 void EmitHeader(cmsContext ContextID, 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(ContextID, hProfile, cmsSigProfileDescriptionTag);
355     Copyright   = (cmsMLU*) cmsReadTag(ContextID, hProfile, cmsSigCopyrightTag);
356 
357     DescASCII[0] = DescASCII[255] = 0;
358     CopyrightASCII[0] = CopyrightASCII[255] = 0;
359 
360     if (Description != NULL) cmsMLUgetASCII(ContextID, Description,  cmsNoLanguage, cmsNoCountry, DescASCII,       255);
361     if (Copyright != NULL)   cmsMLUgetASCII(ContextID, Copyright,    cmsNoLanguage, cmsNoCountry, CopyrightASCII,  255);
362 
363     _cmsIOPrintf(ContextID, m, "%%!PS-Adobe-3.0\n");
364     _cmsIOPrintf(ContextID, m, "%%\n");
365     _cmsIOPrintf(ContextID, m, "%% %s\n", Title);
366     _cmsIOPrintf(ContextID, m, "%% Source: %s\n", RemoveCR(DescASCII));
367     _cmsIOPrintf(ContextID, m, "%%         %s\n", RemoveCR(CopyrightASCII));
368     _cmsIOPrintf(ContextID, m, "%% Created: %s", ctime(&timer)); // ctime appends a \n!!!
369     _cmsIOPrintf(ContextID, m, "%%\n");
370     _cmsIOPrintf(ContextID, 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(cmsContext ContextID,cmsIOHANDLER * m,cmsCIEXYZ * BlackPoint)379 void EmitWhiteBlackD50(cmsContext ContextID, cmsIOHANDLER* m, cmsCIEXYZ* BlackPoint)
380 {
381 
382     _cmsIOPrintf(ContextID, m, "/BlackPoint [%f %f %f]\n", BlackPoint -> X,
383                                           BlackPoint -> Y,
384                                           BlackPoint -> Z);
385 
386     _cmsIOPrintf(ContextID, m, "/WhitePoint [%f %f %f]\n", cmsD50_XYZ(ContextID)->X,
387                                           cmsD50_XYZ(ContextID)->Y,
388                                           cmsD50_XYZ(ContextID)->Z);
389 }
390 
391 
392 static
EmitRangeCheck(cmsContext ContextID,cmsIOHANDLER * m)393 void EmitRangeCheck(cmsContext ContextID, cmsIOHANDLER* m)
394 {
395     _cmsIOPrintf(ContextID, 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(cmsContext ContextID,cmsIOHANDLER * m,cmsUInt32Number RenderingIntent)403 void EmitIntent(cmsContext ContextID, cmsIOHANDLER* m, cmsUInt32Number 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(ContextID, 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(ContextID, 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(cmsContext ContextID,cmsIOHANDLER * m)445 void EmitLab2XYZ(cmsContext ContextID, cmsIOHANDLER* m)
446 {
447     _cmsIOPrintf(ContextID, m, "/RangeABC [ 0 1 0 1 0 1]\n");
448     _cmsIOPrintf(ContextID, m, "/DecodeABC [\n");
449     _cmsIOPrintf(ContextID, m, "{100 mul  16 add 116 div } bind\n");
450     _cmsIOPrintf(ContextID, m, "{255 mul 128 sub 500 div } bind\n");
451     _cmsIOPrintf(ContextID, m, "{255 mul 128 sub 200 div } bind\n");
452     _cmsIOPrintf(ContextID, m, "]\n");
453     _cmsIOPrintf(ContextID, m, "/MatrixABC [ 1 1 1 1 0 0 0 0 -1]\n");
454     _cmsIOPrintf(ContextID, m, "/RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]\n");
455     _cmsIOPrintf(ContextID, m, "/DecodeLMN [\n");
456     _cmsIOPrintf(ContextID, 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(ContextID, m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind\n");
458     _cmsIOPrintf(ContextID, 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(ContextID, m, "]\n");
460 }
461 
462 
463 
464 // Outputs a table of words. It does use 16 bits
465 
466 static
Emit1Gamma(cmsContext ContextID,cmsIOHANDLER * m,cmsToneCurve * Table)467 void Emit1Gamma(cmsContext ContextID, 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(ContextID, Table)) return;
478 
479     // Check if is really an exponential. If so, emit "exp"
480     gamma = cmsEstimateGamma(ContextID, Table, 0.001);
481      if (gamma > 0) {
482             _cmsIOPrintf(ContextID, m, "{ %g exp } bind ", gamma);
483             return;
484      }
485 
486     _cmsIOPrintf(ContextID, m, "{ ");
487 
488     // Bounds check
489     EmitRangeCheck(ContextID, m);
490 
491     // Emit intepolation code
492 
493     // PostScript code                      Stack
494     // ===============                      ========================
495                                             // v
496     _cmsIOPrintf(ContextID, m, " [");
497 
498     for (i=0; i < Table->nEntries; i++) {
499         _cmsIOPrintf(ContextID, m, "%d ", Table->Table16[i]);
500     }
501 
502     _cmsIOPrintf(ContextID, m, "] ");                        // v tab
503 
504     _cmsIOPrintf(ContextID, m, "dup ");                      // v tab tab
505     _cmsIOPrintf(ContextID, m, "length 1 sub ");             // v tab dom
506     _cmsIOPrintf(ContextID, m, "3 -1 roll ");                // tab dom v
507     _cmsIOPrintf(ContextID, m, "mul ");                      // tab val2
508     _cmsIOPrintf(ContextID, m, "dup ");                      // tab val2 val2
509     _cmsIOPrintf(ContextID, m, "dup ");                      // tab val2 val2 val2
510     _cmsIOPrintf(ContextID, m, "floor cvi ");                // tab val2 val2 cell0
511     _cmsIOPrintf(ContextID, m, "exch ");                     // tab val2 cell0 val2
512     _cmsIOPrintf(ContextID, m, "ceiling cvi ");              // tab val2 cell0 cell1
513     _cmsIOPrintf(ContextID, m, "3 index ");                  // tab val2 cell0 cell1 tab
514     _cmsIOPrintf(ContextID, m, "exch ");                     // tab val2 cell0 tab cell1
515     _cmsIOPrintf(ContextID, m, "get ");                      // tab val2 cell0 y1
516     _cmsIOPrintf(ContextID, m, "4 -1 roll ");                // val2 cell0 y1 tab
517     _cmsIOPrintf(ContextID, m, "3 -1 roll ");                // val2 y1 tab cell0
518     _cmsIOPrintf(ContextID, m, "get ");                      // val2 y1 y0
519     _cmsIOPrintf(ContextID, m, "dup ");                      // val2 y1 y0 y0
520     _cmsIOPrintf(ContextID, m, "3 1 roll ");                 // val2 y0 y1 y0
521     _cmsIOPrintf(ContextID, m, "sub ");                      // val2 y0 (y1-y0)
522     _cmsIOPrintf(ContextID, m, "3 -1 roll ");                // y0 (y1-y0) val2
523     _cmsIOPrintf(ContextID, m, "dup ");                      // y0 (y1-y0) val2 val2
524     _cmsIOPrintf(ContextID, m, "floor cvi ");                // y0 (y1-y0) val2 floor(val2)
525     _cmsIOPrintf(ContextID, m, "sub ");                      // y0 (y1-y0) rest
526     _cmsIOPrintf(ContextID, m, "mul ");                      // y0 t1
527     _cmsIOPrintf(ContextID, m, "add ");                      // y
528     _cmsIOPrintf(ContextID, m, "65535 div ");                // result
529 
530     _cmsIOPrintf(ContextID, m, " } bind ");
531 }
532 
533 
534 // Compare gamma table
535 
536 static
GammaTableEquals(cmsUInt16Number * g1,cmsUInt16Number * g2,cmsUInt32Number nEntries)537 cmsBool GammaTableEquals(cmsUInt16Number* g1, cmsUInt16Number* g2, cmsUInt32Number 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(cmsContext ContextID,cmsIOHANDLER * m,cmsUInt32Number n,cmsToneCurve * g[])546 void EmitNGamma(cmsContext ContextID, cmsIOHANDLER* m, cmsUInt32Number n, cmsToneCurve* g[])
547 {
548     cmsUInt32Number 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(ContextID, m, "dup ");
557         }
558         else {
559             Emit1Gamma(ContextID, 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(cmsContext ContextID,register const cmsUInt16Number In[],register cmsUInt16Number Out[],register void * Cargo)585 int OutputValueSampler(cmsContext ContextID, 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(ContextID, sc ->m, sc ->PostMin);
621                     sc ->SecondComponent = -1;
622                     _cmsIOPrintf(ContextID, sc ->m, sc ->PostMaj);
623             }
624 
625             // Begin block
626             _cmsPSActualColumn = 0;
627 
628             _cmsIOPrintf(ContextID, 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(ContextID, sc ->m, sc ->PostMin);
638             }
639 
640             _cmsIOPrintf(ContextID, 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(ContextID, 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(cmsContext ContextID,cmsIOHANDLER * m,cmsStage * mpe,const char * PreMaj,const char * PostMaj,const char * PreMin,const char * PostMin,int FixWhite,cmsColorSpaceSignature ColorSpace)664 void WriteCLUT(cmsContext ContextID, 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(ContextID, m, "[");
687 
688     for (i=0; i < sc.Pipeline->Params->nInputs; i++)
689         _cmsIOPrintf(ContextID, m, " %d ", sc.Pipeline->Params->nSamples[i]);
690 
691     _cmsIOPrintf(ContextID, m, " [\n");
692 
693     cmsStageSampleCLut16bit(ContextID, mpe, OutputValueSampler, (void*) &sc, SAMPLER_INSPECT);
694 
695     _cmsIOPrintf(ContextID, m, PostMin);
696     _cmsIOPrintf(ContextID, m, PostMaj);
697     _cmsIOPrintf(ContextID, m, "] ");
698 
699 }
700 
701 
702 // Dumps CIEBasedA Color Space Array
703 
704 static
EmitCIEBasedA(cmsContext ContextID,cmsIOHANDLER * m,cmsToneCurve * Curve,cmsCIEXYZ * BlackPoint)705 int EmitCIEBasedA(cmsContext ContextID, cmsIOHANDLER* m, cmsToneCurve* Curve, cmsCIEXYZ* BlackPoint)
706 {
707 
708     _cmsIOPrintf(ContextID, m, "[ /CIEBasedA\n");
709     _cmsIOPrintf(ContextID, m, "  <<\n");
710 
711     _cmsIOPrintf(ContextID, m, "/DecodeA ");
712 
713     Emit1Gamma(ContextID, m, Curve);
714 
715     _cmsIOPrintf(ContextID, m, " \n");
716 
717     _cmsIOPrintf(ContextID, m, "/MatrixA [ 0.9642 1.0000 0.8249 ]\n");
718     _cmsIOPrintf(ContextID, m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
719 
720     EmitWhiteBlackD50(ContextID, m, BlackPoint);
721     EmitIntent(ContextID, m, INTENT_PERCEPTUAL);
722 
723     _cmsIOPrintf(ContextID, m, ">>\n");
724     _cmsIOPrintf(ContextID, m, "]\n");
725 
726     return 1;
727 }
728 
729 
730 // Dumps CIEBasedABC Color Space Array
731 
732 static
EmitCIEBasedABC(cmsContext ContextID,cmsIOHANDLER * m,cmsFloat64Number * Matrix,cmsToneCurve ** CurveSet,cmsCIEXYZ * BlackPoint)733 int EmitCIEBasedABC(cmsContext ContextID, cmsIOHANDLER* m, cmsFloat64Number* Matrix, cmsToneCurve** CurveSet, cmsCIEXYZ* BlackPoint)
734 {
735     int i;
736 
737     _cmsIOPrintf(ContextID, m, "[ /CIEBasedABC\n");
738     _cmsIOPrintf(ContextID, m, "<<\n");
739     _cmsIOPrintf(ContextID, m, "/DecodeABC [ ");
740 
741     EmitNGamma(ContextID, m, 3, CurveSet);
742 
743     _cmsIOPrintf(ContextID, m, "]\n");
744 
745     _cmsIOPrintf(ContextID, m, "/MatrixABC [ " );
746 
747     for( i=0; i < 3; i++ ) {
748 
749         _cmsIOPrintf(ContextID, m, "%.6f %.6f %.6f ", Matrix[i + 3*0],
750                                            Matrix[i + 3*1],
751                                            Matrix[i + 3*2]);
752     }
753 
754 
755     _cmsIOPrintf(ContextID, m, "]\n");
756 
757     _cmsIOPrintf(ContextID, m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
758 
759     EmitWhiteBlackD50(ContextID, m, BlackPoint);
760     EmitIntent(ContextID, m, INTENT_PERCEPTUAL);
761 
762     _cmsIOPrintf(ContextID, m, ">>\n");
763     _cmsIOPrintf(ContextID, m, "]\n");
764 
765 
766     return 1;
767 }
768 
769 
770 static
EmitCIEBasedDEF(cmsContext ContextID,cmsIOHANDLER * m,cmsPipeline * Pipeline,cmsUInt32Number Intent,cmsCIEXYZ * BlackPoint)771 int EmitCIEBasedDEF(cmsContext ContextID, cmsIOHANDLER* m, cmsPipeline* Pipeline, cmsUInt32Number 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(ContextID, mpe)) {
781     case 3:
782 
783             _cmsIOPrintf(ContextID, m, "[ /CIEBasedDEF\n");
784             PreMaj ="<";
785             PostMaj= ">\n";
786             PreMin = PostMin = "";
787             break;
788     case 4:
789             _cmsIOPrintf(ContextID, 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(ContextID, m, "<<\n");
801 
802     if (cmsStageType(ContextID, mpe) == cmsSigCurveSetElemType) {
803 
804         _cmsIOPrintf(ContextID, m, "/DecodeDEF [ ");
805         EmitNGamma(ContextID, m, cmsStageOutputChannels(ContextID, mpe), _cmsStageGetPtrToCurveSet(mpe));
806         _cmsIOPrintf(ContextID, m, "]\n");
807 
808         mpe = mpe ->Next;
809     }
810 
811     if (cmsStageType(ContextID, mpe) == cmsSigCLutElemType) {
812 
813             _cmsIOPrintf(ContextID, m, "/Table ");
814             WriteCLUT(ContextID, m, mpe, PreMaj, PostMaj, PreMin, PostMin, FALSE, (cmsColorSpaceSignature) 0);
815             _cmsIOPrintf(ContextID, m, "]\n");
816     }
817 
818     EmitLab2XYZ(ContextID, m);
819     EmitWhiteBlackD50(ContextID, m, BlackPoint);
820     EmitIntent(ContextID, m, Intent);
821 
822     _cmsIOPrintf(ContextID, m, "   >>\n");
823     _cmsIOPrintf(ContextID, m, "]\n");
824 
825     return 1;
826 }
827 
828 // Generates a curve from a gray profile
829 
830 static
ExtractGray2Y(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent)831 cmsToneCurve* ExtractGray2Y(cmsContext ContextID, cmsHPROFILE hProfile, cmsUInt32Number Intent)
832 {
833     cmsToneCurve* Out = cmsBuildTabulatedToneCurve16(ContextID, 256, NULL);
834     cmsHPROFILE hXYZ  = cmsCreateXYZProfile(ContextID);
835     cmsHTRANSFORM xform = cmsCreateTransform(ContextID, hProfile, TYPE_GRAY_8, hXYZ, TYPE_XYZ_DBL, Intent, cmsFLAGS_NOOPTIMIZE);
836     int i;
837 
838     if (Out != NULL && xform != NULL) {
839         for (i=0; i < 256; i++) {
840 
841             cmsUInt8Number Gray = (cmsUInt8Number) i;
842             cmsCIEXYZ XYZ;
843 
844             cmsDoTransform(ContextID, xform, &Gray, &XYZ, 1);
845 
846             Out ->Table16[i] =_cmsQuickSaturateWord(XYZ.Y * 65535.0);
847         }
848     }
849 
850     if (xform) cmsDeleteTransform(ContextID, xform);
851     if (hXYZ) cmsCloseProfile(ContextID, 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(cmsContext ContextID,cmsIOHANDLER * m,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags)861 int WriteInputLUT(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number 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(ContextID, hProfile, 2, FALSE);
875     nChannels   = T_CHANNELS(InputFormat);
876 
877 
878     cmsDetectBlackPoint(ContextID, &BlackPointAdaptedToD50, hProfile, Intent, 0);
879 
880     // Adjust output to Lab4
881     hLab = cmsCreateLab4Profile(ContextID, NULL);
882 
883     Profiles[0] = hProfile;
884     Profiles[1] = hLab;
885 
886     xform = cmsCreateMultiprofileTransform(ContextID, Profiles, 2,  InputFormat, TYPE_Lab_DBL, Intent, 0);
887     cmsCloseProfile(ContextID, hLab);
888 
889     if (xform == NULL) {
890 
891         cmsSignalError(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(ContextID, hProfile, Intent);
901             EmitCIEBasedA(ContextID, m, Gray2Y, &BlackPointAdaptedToD50);
902             cmsFreeToneCurve(ContextID, 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(ContextID, v ->core->Lut);
913             if (DeviceLink == NULL) return 0;
914 
915             dwFlags |= cmsFLAGS_FORCE_CLUT;
916             _cmsOptimizePipeline(ContextID, &DeviceLink, Intent, &InputFormat, &OutFrm, &dwFlags);
917 
918             rc = EmitCIEBasedDEF(ContextID, m, DeviceLink, Intent, &BlackPointAdaptedToD50);
919             cmsPipelineFree(ContextID, DeviceLink);
920             if (rc == 0) return 0;
921             }
922             break;
923 
924     default:
925 
926         cmsSignalError(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(ContextID, 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 static
WriteInputMatrixShaper(cmsContext ContextID,cmsIOHANDLER * m,cmsHPROFILE hProfile,cmsStage * Matrix,cmsStage * Shaper)947 int WriteInputMatrixShaper(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsStage* Matrix, cmsStage* Shaper)
948 {
949     cmsColorSpaceSignature ColorSpace;
950     int rc;
951     cmsCIEXYZ BlackPointAdaptedToD50;
952 
953     ColorSpace = cmsGetColorSpace(ContextID, hProfile);
954 
955     cmsDetectBlackPoint(ContextID, &BlackPointAdaptedToD50, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0);
956 
957     if (ColorSpace == cmsSigGrayData) {
958 
959         cmsToneCurve** ShaperCurve = _cmsStageGetPtrToCurveSet(Shaper);
960         rc = EmitCIEBasedA(ContextID, m, ShaperCurve[0], &BlackPointAdaptedToD50);
961 
962     }
963     else
964         if (ColorSpace == cmsSigRgbData) {
965 
966             cmsMAT3 Mat;
967             int i, j;
968 
969             memmove(&Mat, GetPtrToMatrix(Matrix), sizeof(Mat));
970 
971             for (i = 0; i < 3; i++)
972                 for (j = 0; j < 3; j++)
973                     Mat.v[i].n[j] *= MAX_ENCODEABLE_XYZ;
974 
975             rc = EmitCIEBasedABC(ContextID, m, (cmsFloat64Number *) &Mat,
976                 _cmsStageGetPtrToCurveSet(Shaper),
977                 &BlackPointAdaptedToD50);
978         }
979         else {
980 
981             cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Profile is not suitable for CSA. Unsupported colorspace.");
982             return 0;
983         }
984 
985     return rc;
986 }
987 
988 
989 
990 // Creates a PostScript color list from a named profile data.
991 // This is a HP extension, and it works in Lab instead of XYZ
992 
993 static
WriteNamedColorCSA(cmsContext ContextID,cmsIOHANDLER * m,cmsHPROFILE hNamedColor,cmsUInt32Number Intent)994 int WriteNamedColorCSA(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent)
995 {
996     cmsHTRANSFORM xform;
997     cmsHPROFILE   hLab;
998     cmsUInt32Number i, nColors;
999     char ColorName[cmsMAX_PATH];
1000     cmsNAMEDCOLORLIST* NamedColorList;
1001 
1002     hLab  = cmsCreateLab4Profile(ContextID, NULL);
1003     xform = cmsCreateTransform(ContextID, hNamedColor, TYPE_NAMED_COLOR_INDEX, hLab, TYPE_Lab_DBL, Intent, 0);
1004     if (xform == NULL) return 0;
1005 
1006     NamedColorList = cmsGetNamedColorList(xform);
1007     if (NamedColorList == NULL) return 0;
1008 
1009     _cmsIOPrintf(ContextID, m, "<<\n");
1010     _cmsIOPrintf(ContextID, m, "(colorlistcomment) (%s)\n", "Named color CSA");
1011     _cmsIOPrintf(ContextID, m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
1012     _cmsIOPrintf(ContextID, m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");
1013 
1014     nColors   = cmsNamedColorCount(ContextID, NamedColorList);
1015 
1016 
1017     for (i=0; i < nColors; i++) {
1018 
1019         cmsUInt16Number In[1];
1020         cmsCIELab Lab;
1021 
1022         In[0] = (cmsUInt16Number) i;
1023 
1024         if (!cmsNamedColorInfo(ContextID, NamedColorList, i, ColorName, NULL, NULL, NULL, NULL))
1025                 continue;
1026 
1027         cmsDoTransform(ContextID, xform, In, &Lab, 1);
1028         _cmsIOPrintf(ContextID, m, "  (%s) [ %.3f %.3f %.3f ]\n", ColorName, Lab.L, Lab.a, Lab.b);
1029     }
1030 
1031 
1032 
1033     _cmsIOPrintf(ContextID, m, ">>\n");
1034 
1035     cmsDeleteTransform(ContextID, xform);
1036     cmsCloseProfile(ContextID, hLab);
1037     return 1;
1038 }
1039 
1040 
1041 // Does create a Color Space Array on XYZ colorspace for PostScript usage
1042 static
GenerateCSA(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,cmsIOHANDLER * mem)1043 cmsUInt32Number GenerateCSA(cmsContext ContextID,
1044                             cmsHPROFILE hProfile,
1045                             cmsUInt32Number Intent,
1046                             cmsUInt32Number dwFlags,
1047                             cmsIOHANDLER* mem)
1048 {
1049     cmsUInt32Number dwBytesUsed;
1050     cmsPipeline* lut = NULL;
1051     cmsStage* Matrix, *Shaper;
1052 
1053 
1054     // Is a named color profile?
1055     if (cmsGetDeviceClass(ContextID, hProfile) == cmsSigNamedColorClass) {
1056 
1057         if (!WriteNamedColorCSA(ContextID, mem, hProfile, Intent)) goto Error;
1058     }
1059     else {
1060 
1061 
1062         // Any profile class are allowed (including devicelink), but
1063         // output (PCS) colorspace must be XYZ or Lab
1064         cmsColorSpaceSignature ColorSpace = cmsGetPCS(ContextID, hProfile);
1065 
1066         if (ColorSpace != cmsSigXYZData &&
1067             ColorSpace != cmsSigLabData) {
1068 
1069                 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Invalid output color space");
1070                 goto Error;
1071         }
1072 
1073 
1074         // Read the lut with all necessary conversion stages
1075         lut = _cmsReadInputLUT(ContextID, hProfile, Intent);
1076         if (lut == NULL) goto Error;
1077 
1078 
1079         // Tone curves + matrix can be implemented without any LUT
1080         if (cmsPipelineCheckAndRetreiveStages(ContextID, lut, 2, cmsSigCurveSetElemType, cmsSigMatrixElemType, &Shaper, &Matrix)) {
1081 
1082             if (!WriteInputMatrixShaper(ContextID, mem, hProfile, Matrix, Shaper)) goto Error;
1083 
1084         }
1085         else {
1086            // We need a LUT for the rest
1087            if (!WriteInputLUT(ContextID, mem, hProfile, Intent, dwFlags)) goto Error;
1088         }
1089     }
1090 
1091 
1092     // Done, keep memory usage
1093     dwBytesUsed = mem ->UsedSpace;
1094 
1095     // Get rid of LUT
1096     if (lut != NULL) cmsPipelineFree(ContextID, lut);
1097 
1098     // Finally, return used byte count
1099     return dwBytesUsed;
1100 
1101 Error:
1102     if (lut != NULL) cmsPipelineFree(ContextID, lut);
1103     return 0;
1104 }
1105 
1106 // ------------------------------------------------------ Color Rendering Dictionary (CRD)
1107 
1108 
1109 
1110 /*
1111 
1112   Black point compensation plus chromatic adaptation:
1113 
1114   Step 1 - Chromatic adaptation
1115   =============================
1116 
1117           WPout
1118     X = ------- PQR
1119           Wpin
1120 
1121   Step 2 - Black point compensation
1122   =================================
1123 
1124           (WPout - BPout)*X - WPout*(BPin - BPout)
1125     out = ---------------------------------------
1126                         WPout - BPin
1127 
1128 
1129   Algorithm discussion
1130   ====================
1131 
1132   TransformPQR(WPin, BPin, WPout, BPout, PQR)
1133 
1134   Wpin,etc= { Xws Yws Zws Pws Qws Rws }
1135 
1136 
1137   Algorithm             Stack 0...n
1138   ===========================================================
1139                         PQR BPout WPout BPin WPin
1140   4 index 3 get         WPin PQR BPout WPout BPin WPin
1141   div                   (PQR/WPin) BPout WPout BPin WPin
1142   2 index 3 get         WPout (PQR/WPin) BPout WPout BPin WPin
1143   mult                  WPout*(PQR/WPin) BPout WPout BPin WPin
1144 
1145   2 index 3 get         WPout WPout*(PQR/WPin) BPout WPout BPin WPin
1146   2 index 3 get         BPout WPout WPout*(PQR/WPin) BPout WPout BPin WPin
1147   sub                   (WPout-BPout) WPout*(PQR/WPin) BPout WPout BPin WPin
1148   mult                  (WPout-BPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1149 
1150   2 index 3 get         WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1151   4 index 3 get         BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1152   3 index 3 get         BPout BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1153 
1154   sub                   (BPin-BPout) WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1155   mult                  (BPin-BPout)*WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
1156   sub                   (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1157 
1158   3 index 3 get         BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1159   3 index 3 get         WPout BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1160   exch
1161   sub                   (WPout-BPin) (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
1162   div
1163 
1164   exch pop
1165   exch pop
1166   exch pop
1167   exch pop
1168 
1169 */
1170 
1171 
1172 static
EmitPQRStage(cmsContext ContextID,cmsIOHANDLER * m,cmsHPROFILE hProfile,int DoBPC,int lIsAbsolute)1173 void EmitPQRStage(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hProfile, int DoBPC, int lIsAbsolute)
1174 {
1175 
1176 
1177         if (lIsAbsolute) {
1178 
1179             // For absolute colorimetric intent, encode back to relative
1180             // and generate a relative Pipeline
1181 
1182             // Relative encoding is obtained across XYZpcs*(D50/WhitePoint)
1183 
1184             cmsCIEXYZ White;
1185 
1186             _cmsReadMediaWhitePoint(ContextID, &White, hProfile);
1187 
1188             _cmsIOPrintf(ContextID, m,"/MatrixPQR [1 0 0 0 1 0 0 0 1 ]\n");
1189             _cmsIOPrintf(ContextID, m,"/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");
1190 
1191             _cmsIOPrintf(ContextID, m, "%% Absolute colorimetric -- encode to relative to maximize LUT usage\n"
1192                       "/TransformPQR [\n"
1193                       "{0.9642 mul %g div exch pop exch pop exch pop exch pop} bind\n"
1194                       "{1.0000 mul %g div exch pop exch pop exch pop exch pop} bind\n"
1195                       "{0.8249 mul %g div exch pop exch pop exch pop exch pop} bind\n]\n",
1196                       White.X, White.Y, White.Z);
1197             return;
1198         }
1199 
1200 
1201         _cmsIOPrintf(ContextID, m,"%% Bradford Cone Space\n"
1202                  "/MatrixPQR [0.8951 -0.7502 0.0389 0.2664 1.7135 -0.0685 -0.1614 0.0367 1.0296 ] \n");
1203 
1204         _cmsIOPrintf(ContextID, m, "/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");
1205 
1206 
1207         // No BPC
1208 
1209         if (!DoBPC) {
1210 
1211             _cmsIOPrintf(ContextID, m, "%% VonKries-like transform in Bradford Cone Space\n"
1212                       "/TransformPQR [\n"
1213                       "{exch pop exch 3 get mul exch pop exch 3 get div} bind\n"
1214                       "{exch pop exch 4 get mul exch pop exch 4 get div} bind\n"
1215                       "{exch pop exch 5 get mul exch pop exch 5 get div} bind\n]\n");
1216         } else {
1217 
1218             // BPC
1219 
1220             _cmsIOPrintf(ContextID, m, "%% VonKries-like transform in Bradford Cone Space plus BPC\n"
1221                       "/TransformPQR [\n");
1222 
1223             _cmsIOPrintf(ContextID, m, "{4 index 3 get div 2 index 3 get mul "
1224                     "2 index 3 get 2 index 3 get sub mul "
1225                     "2 index 3 get 4 index 3 get 3 index 3 get sub mul sub "
1226                     "3 index 3 get 3 index 3 get exch sub div "
1227                     "exch pop exch pop exch pop exch pop } bind\n");
1228 
1229             _cmsIOPrintf(ContextID, m, "{4 index 4 get div 2 index 4 get mul "
1230                     "2 index 4 get 2 index 4 get sub mul "
1231                     "2 index 4 get 4 index 4 get 3 index 4 get sub mul sub "
1232                     "3 index 4 get 3 index 4 get exch sub div "
1233                     "exch pop exch pop exch pop exch pop } bind\n");
1234 
1235             _cmsIOPrintf(ContextID, m, "{4 index 5 get div 2 index 5 get mul "
1236                     "2 index 5 get 2 index 5 get sub mul "
1237                     "2 index 5 get 4 index 5 get 3 index 5 get sub mul sub "
1238                     "3 index 5 get 3 index 5 get exch sub div "
1239                     "exch pop exch pop exch pop exch pop } bind\n]\n");
1240 
1241         }
1242 
1243 
1244 }
1245 
1246 
1247 static
EmitXYZ2Lab(cmsContext ContextID,cmsIOHANDLER * m)1248 void EmitXYZ2Lab(cmsContext ContextID, cmsIOHANDLER* m)
1249 {
1250     _cmsIOPrintf(ContextID, m, "/RangeLMN [ -0.635 2.0 0 2 -0.635 2.0 ]\n");
1251     _cmsIOPrintf(ContextID, m, "/EncodeLMN [\n");
1252     _cmsIOPrintf(ContextID, m, "{ 0.964200  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1253     _cmsIOPrintf(ContextID, m, "{ 1.000000  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1254     _cmsIOPrintf(ContextID, m, "{ 0.824900  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
1255     _cmsIOPrintf(ContextID, m, "]\n");
1256     _cmsIOPrintf(ContextID, m, "/MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]\n");
1257     _cmsIOPrintf(ContextID, m, "/EncodeABC [\n");
1258 
1259 
1260     _cmsIOPrintf(ContextID, m, "{ 116 mul  16 sub 100 div  } bind\n");
1261     _cmsIOPrintf(ContextID, m, "{ 500 mul 128 add 256 div  } bind\n");
1262     _cmsIOPrintf(ContextID, m, "{ 200 mul 128 add 256 div  } bind\n");
1263 
1264 
1265     _cmsIOPrintf(ContextID, m, "]\n");
1266 
1267 
1268 }
1269 
1270 // Due to impedance mismatch between XYZ and almost all RGB and CMYK spaces
1271 // I choose to dump LUTS in Lab instead of XYZ. There is still a lot of wasted
1272 // space on 3D CLUT, but since space seems not to be a problem here, 33 points
1273 // would give a reasonable accuracy. Note also that CRD tables must operate in
1274 // 8 bits.
1275 
1276 static
WriteOutputLUT(cmsContext ContextID,cmsIOHANDLER * m,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags)1277 int WriteOutputLUT(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
1278 {
1279     cmsHPROFILE hLab;
1280     cmsHTRANSFORM xform;
1281     cmsUInt32Number i, nChannels;
1282     cmsUInt32Number OutputFormat;
1283     _cmsTRANSFORM* v;
1284     cmsPipeline* DeviceLink;
1285     cmsHPROFILE Profiles[3];
1286     cmsCIEXYZ BlackPointAdaptedToD50;
1287     cmsBool lDoBPC = (cmsBool) (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION);
1288     cmsBool lFixWhite = (cmsBool) !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP);
1289     cmsUInt32Number InFrm = TYPE_Lab_16;
1290     cmsUInt32Number RelativeEncodingIntent;
1291     cmsColorSpaceSignature ColorSpace;
1292 
1293 
1294     hLab = cmsCreateLab4Profile(ContextID, NULL);
1295     if (hLab == NULL) return 0;
1296 
1297     OutputFormat = cmsFormatterForColorspaceOfProfile(ContextID, hProfile, 2, FALSE);
1298     nChannels    = T_CHANNELS(OutputFormat);
1299 
1300     ColorSpace = cmsGetColorSpace(ContextID, hProfile);
1301 
1302     // For absolute colorimetric, the LUT is encoded as relative in order to preserve precision.
1303 
1304     RelativeEncodingIntent = Intent;
1305     if (RelativeEncodingIntent == INTENT_ABSOLUTE_COLORIMETRIC)
1306         RelativeEncodingIntent = INTENT_RELATIVE_COLORIMETRIC;
1307 
1308 
1309     // Use V4 Lab always
1310     Profiles[0] = hLab;
1311     Profiles[1] = hProfile;
1312 
1313     xform = cmsCreateMultiprofileTransform(ContextID,
1314                                               Profiles, 2, TYPE_Lab_DBL,
1315                                               OutputFormat, RelativeEncodingIntent, 0);
1316     cmsCloseProfile(ContextID, hLab);
1317 
1318     if (xform == NULL) {
1319 
1320         cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Lab -> Profile in CRD creation");
1321         return 0;
1322     }
1323 
1324     // Get a copy of the internal devicelink
1325     v = (_cmsTRANSFORM*) xform;
1326     DeviceLink = cmsPipelineDup(ContextID, v ->core->Lut);
1327     if (DeviceLink == NULL) return 0;
1328 
1329 
1330     // We need a CLUT
1331     dwFlags |= cmsFLAGS_FORCE_CLUT;
1332     _cmsOptimizePipeline(ContextID, &DeviceLink, RelativeEncodingIntent, &InFrm, &OutputFormat, &dwFlags);
1333 
1334     _cmsIOPrintf(ContextID, m, "<<\n");
1335     _cmsIOPrintf(ContextID, m, "/ColorRenderingType 1\n");
1336 
1337 
1338     cmsDetectBlackPoint(ContextID, &BlackPointAdaptedToD50, hProfile, Intent, 0);
1339 
1340     // Emit headers, etc.
1341     EmitWhiteBlackD50(ContextID, m, &BlackPointAdaptedToD50);
1342     EmitPQRStage(ContextID, m, hProfile, lDoBPC, Intent == INTENT_ABSOLUTE_COLORIMETRIC);
1343     EmitXYZ2Lab(ContextID, m);
1344 
1345 
1346     // FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab
1347     // does map a=b=0 not falling into any specific node. Since range a,b goes -128..127,
1348     // zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to
1349     // zero. This would sacrifice a bit of highlights, but failure to do so would cause
1350     // scum dot. Ouch.
1351 
1352     if (Intent == INTENT_ABSOLUTE_COLORIMETRIC)
1353             lFixWhite = FALSE;
1354 
1355     _cmsIOPrintf(ContextID, m, "/RenderTable ");
1356 
1357 
1358     WriteCLUT(ContextID, m, cmsPipelineGetPtrToFirstStage(ContextID, DeviceLink), "<", ">\n", "", "", lFixWhite, ColorSpace);
1359 
1360     _cmsIOPrintf(ContextID, m, " %d {} bind ", nChannels);
1361 
1362     for (i=1; i < nChannels; i++)
1363             _cmsIOPrintf(ContextID, m, "dup ");
1364 
1365     _cmsIOPrintf(ContextID, m, "]\n");
1366 
1367 
1368     EmitIntent(ContextID, m, Intent);
1369 
1370     _cmsIOPrintf(ContextID, m, ">>\n");
1371 
1372     if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1373 
1374         _cmsIOPrintf(ContextID, m, "/Current exch /ColorRendering defineresource pop\n");
1375     }
1376 
1377     cmsPipelineFree(ContextID, DeviceLink);
1378     cmsDeleteTransform(ContextID, xform);
1379 
1380     return 1;
1381 }
1382 
1383 
1384 // Builds a ASCII string containing colorant list in 0..1.0 range
1385 static
BuildColorantList(char * Colorant,cmsUInt32Number nColorant,cmsUInt16Number Out[])1386 void BuildColorantList(char *Colorant, cmsUInt32Number nColorant, cmsUInt16Number Out[])
1387 {
1388     char Buff[32];
1389     cmsUInt32Number j;
1390 
1391     Colorant[0] = 0;
1392     if (nColorant > cmsMAXCHANNELS)
1393         nColorant = cmsMAXCHANNELS;
1394 
1395     for (j = 0; j < nColorant; j++) {
1396 
1397         snprintf(Buff, 31, "%.3f", Out[j] / 65535.0);
1398         Buff[31] = 0;
1399         strcat(Colorant, Buff);
1400         if (j < nColorant - 1)
1401             strcat(Colorant, " ");
1402 
1403     }
1404 }
1405 
1406 
1407 // Creates a PostScript color list from a named profile data.
1408 // This is a HP extension.
1409 
1410 static
WriteNamedColorCRD(cmsContext ContextID,cmsIOHANDLER * m,cmsHPROFILE hNamedColor,cmsUInt32Number Intent,cmsUInt32Number dwFlags)1411 int WriteNamedColorCRD(cmsContext ContextID, cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
1412 {
1413     cmsHTRANSFORM xform;
1414     cmsUInt32Number i, nColors, nColorant;
1415     cmsUInt32Number OutputFormat;
1416     char ColorName[cmsMAX_PATH];
1417     char Colorant[128];
1418     cmsNAMEDCOLORLIST* NamedColorList;
1419 
1420 
1421     OutputFormat = cmsFormatterForColorspaceOfProfile(ContextID, hNamedColor, 2, FALSE);
1422     nColorant    = T_CHANNELS(OutputFormat);
1423 
1424 
1425     xform = cmsCreateTransform(ContextID, hNamedColor, TYPE_NAMED_COLOR_INDEX, NULL, OutputFormat, Intent, dwFlags);
1426     if (xform == NULL) return 0;
1427 
1428 
1429     NamedColorList = cmsGetNamedColorList(xform);
1430     if (NamedColorList == NULL) return 0;
1431 
1432     _cmsIOPrintf(ContextID, m, "<<\n");
1433     _cmsIOPrintf(ContextID, m, "(colorlistcomment) (%s) \n", "Named profile");
1434     _cmsIOPrintf(ContextID, m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
1435     _cmsIOPrintf(ContextID, m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");
1436 
1437     nColors   = cmsNamedColorCount(ContextID, NamedColorList);
1438 
1439     for (i=0; i < nColors; i++) {
1440 
1441         cmsUInt16Number In[1];
1442         cmsUInt16Number Out[cmsMAXCHANNELS];
1443 
1444         In[0] = (cmsUInt16Number) i;
1445 
1446         if (!cmsNamedColorInfo(ContextID, NamedColorList, i, ColorName, NULL, NULL, NULL, NULL))
1447                 continue;
1448 
1449         cmsDoTransform(ContextID, xform, In, Out, 1);
1450         BuildColorantList(Colorant, nColorant, Out);
1451         _cmsIOPrintf(ContextID, m, "  (%s) [ %s ]\n", ColorName, Colorant);
1452     }
1453 
1454     _cmsIOPrintf(ContextID, m, "   >>");
1455 
1456     if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1457 
1458     _cmsIOPrintf(ContextID, m, " /Current exch /HPSpotTable defineresource pop\n");
1459     }
1460 
1461     cmsDeleteTransform(ContextID, xform);
1462     return 1;
1463 }
1464 
1465 
1466 
1467 // This one does create a Color Rendering Dictionary.
1468 // CRD are always LUT-Based, no matter if profile is
1469 // implemented as matrix-shaper.
1470 
1471 static
GenerateCRD(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,cmsIOHANDLER * mem)1472 cmsUInt32Number  GenerateCRD(cmsContext ContextID,
1473                              cmsHPROFILE hProfile,
1474                              cmsUInt32Number Intent, cmsUInt32Number dwFlags,
1475                              cmsIOHANDLER* mem)
1476 {
1477     cmsUInt32Number dwBytesUsed;
1478 
1479     if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1480 
1481         EmitHeader(ContextID, mem, "Color Rendering Dictionary (CRD)", hProfile);
1482     }
1483 
1484 
1485     // Is a named color profile?
1486     if (cmsGetDeviceClass(ContextID, hProfile) == cmsSigNamedColorClass) {
1487 
1488         if (!WriteNamedColorCRD(ContextID, mem, hProfile, Intent, dwFlags)) {
1489             return 0;
1490         }
1491     }
1492     else {
1493 
1494         // CRD are always implemented as LUT
1495 
1496         if (!WriteOutputLUT(ContextID, mem, hProfile, Intent, dwFlags)) {
1497             return 0;
1498         }
1499     }
1500 
1501     if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
1502 
1503         _cmsIOPrintf(ContextID, mem, "%%%%EndResource\n");
1504         _cmsIOPrintf(ContextID, mem, "\n%% CRD End\n");
1505     }
1506 
1507     // Done, keep memory usage
1508     dwBytesUsed = mem ->UsedSpace;
1509 
1510     // Finally, return used byte count
1511     return dwBytesUsed;
1512 }
1513 
1514 
1515 
1516 
cmsGetPostScriptColorResource(cmsContext ContextID,cmsPSResourceType Type,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,cmsIOHANDLER * io)1517 cmsUInt32Number CMSEXPORT cmsGetPostScriptColorResource(cmsContext ContextID,
1518                                                                cmsPSResourceType Type,
1519                                                                cmsHPROFILE hProfile,
1520                                                                cmsUInt32Number Intent,
1521                                                                cmsUInt32Number dwFlags,
1522                                                                cmsIOHANDLER* io)
1523 {
1524     cmsUInt32Number  rc;
1525 
1526 
1527     switch (Type) {
1528 
1529         case cmsPS_RESOURCE_CSA:
1530             rc = GenerateCSA(ContextID, hProfile, Intent, dwFlags, io);
1531             break;
1532 
1533         default:
1534         case cmsPS_RESOURCE_CRD:
1535             rc = GenerateCRD(ContextID, hProfile, Intent, dwFlags, io);
1536             break;
1537     }
1538 
1539     return rc;
1540 }
1541 
1542 
1543 
cmsGetPostScriptCRD(cmsContext ContextID,cmsHPROFILE hProfile,cmsUInt32Number Intent,cmsUInt32Number dwFlags,void * Buffer,cmsUInt32Number dwBufferLen)1544 cmsUInt32Number CMSEXPORT cmsGetPostScriptCRD(cmsContext ContextID,
1545                               cmsHPROFILE hProfile,
1546                               cmsUInt32Number Intent, cmsUInt32Number dwFlags,
1547                               void* Buffer, cmsUInt32Number dwBufferLen)
1548 {
1549     cmsIOHANDLER* mem;
1550     cmsUInt32Number dwBytesUsed;
1551 
1552     // Set up the serialization engine
1553     if (Buffer == NULL)
1554         mem = cmsOpenIOhandlerFromNULL(ContextID);
1555     else
1556         mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w");
1557 
1558     if (!mem) return 0;
1559 
1560     dwBytesUsed =  cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CRD, hProfile, Intent, dwFlags, mem);
1561 
1562     // Get rid of memory stream
1563     cmsCloseIOhandler(ContextID, mem);
1564 
1565     return dwBytesUsed;
1566 }
1567 
1568 
1569 
1570 // 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)1571 cmsUInt32Number CMSEXPORT cmsGetPostScriptCSA(cmsContext ContextID,
1572                                               cmsHPROFILE hProfile,
1573                                               cmsUInt32Number Intent,
1574                                               cmsUInt32Number dwFlags,
1575                                               void* Buffer,
1576                                               cmsUInt32Number dwBufferLen)
1577 {
1578     cmsIOHANDLER* mem;
1579     cmsUInt32Number dwBytesUsed;
1580 
1581     if (Buffer == NULL)
1582         mem = cmsOpenIOhandlerFromNULL(ContextID);
1583     else
1584         mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w");
1585 
1586     if (!mem) return 0;
1587 
1588     dwBytesUsed =  cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CSA, hProfile, Intent, dwFlags, mem);
1589 
1590     // Get rid of memory stream
1591     cmsCloseIOhandler(ContextID, mem);
1592 
1593     return dwBytesUsed;
1594 
1595 }
1596