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