Coverage Report

Created: 2026-08-14 08:24

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