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