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