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