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