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