comparison mupdf-source/thirdparty/lcms2/src/cmsps2.c @ 2:b50eed0cc0ef upstream

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