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