Coverage Report

Created: 2025-12-18 06:39

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