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 810 switch (cmsStageInputChannels(mpe)) { 811 case 3: 812 813 _cmsIOPrintf(m, "[ /CIEBasedDEF\n"); 814 PreMaj ="<"; 815 PostMaj= ">\n"; 816 PreMin = PostMin = ""; 817 break; 818 case 4: 819 _cmsIOPrintf(m, "[ /CIEBasedDEFG\n"); 820 PreMaj = "["; 821 PostMaj = "]\n"; 822 PreMin = "<"; 823 PostMin = ">\n"; 824 break; 825 default: 826 return 0; 827 828 } 829 830 _cmsIOPrintf(m, "<<\n"); 831 832 if (cmsStageType(mpe) == cmsSigCurveSetElemType) { 833 834 _cmsIOPrintf(m, "/DecodeDEF [ "); 835 EmitNGamma(m, cmsStageOutputChannels(mpe), _cmsStageGetPtrToCurveSet(mpe)); 836 _cmsIOPrintf(m, "]\n"); 837 838 mpe = mpe ->Next; 839 } 840 841 842 843 if (cmsStageType(mpe) == cmsSigCLutElemType) { 844 845 _cmsIOPrintf(m, "/Table "); 846 WriteCLUT(m, mpe, PreMaj, PostMaj, PreMin, PostMin, FALSE, (cmsColorSpaceSignature) 0); 847 _cmsIOPrintf(m, "]\n"); 848 } 849 850 EmitLab2XYZ(m); 851 EmitWhiteBlackD50(m, BlackPoint); 852 EmitIntent(m, Intent); 853 854 _cmsIOPrintf(m, " >>\n"); 855 _cmsIOPrintf(m, "]\n"); 856 857 858 return 1; 859 } 860 861 // Generates a curve from a gray profile 862 863 static 864 cmsToneCurve* ExtractGray2Y(cmsContext ContextID, cmsHPROFILE hProfile, int Intent) 865 { 866 cmsToneCurve* Out = cmsBuildTabulatedToneCurve16(ContextID, 256, NULL); 867 cmsHPROFILE hXYZ = cmsCreateXYZProfile(); 868 cmsHTRANSFORM xform = cmsCreateTransformTHR(ContextID, hProfile, TYPE_GRAY_8, hXYZ, TYPE_XYZ_DBL, Intent, cmsFLAGS_NOOPTIMIZE); 869 int i; 870 871 if (Out != NULL) { 872 for (i=0; i < 256; i++) { 873 874 cmsUInt8Number Gray = (cmsUInt8Number) i; 875 cmsCIEXYZ XYZ; 876 877 cmsDoTransform(xform, &Gray, &XYZ, 1); 878 879 Out ->Table16[i] =_cmsQuickSaturateWord(XYZ.Y * 65535.0); 880 } 881 } 882 883 cmsDeleteTransform(xform); 884 cmsCloseProfile(hXYZ); 885 return Out; 886 } 887 888 889 890 // Because PostScript has only 8 bits in /Table, we should use 891 // a more perceptually uniform space... I do choose Lab. 892 893 static 894 int WriteInputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, int Intent, cmsUInt32Number dwFlags) 895 { 896 cmsHPROFILE hLab; 897 cmsHTRANSFORM xform; 898 cmsUInt32Number nChannels; 899 cmsUInt32Number InputFormat; 900 int rc; 901 cmsHPROFILE Profiles[2]; 902 cmsCIEXYZ BlackPointAdaptedToD50; 903 904 // Does create a device-link based transform. 905 // The DeviceLink is next dumped as working CSA. 906 907 InputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE); 908 nChannels = T_CHANNELS(InputFormat); 909 910 911 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0); 912 913 // Adjust output to Lab4 914 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL); 915 916 Profiles[0] = hProfile; 917 Profiles[1] = hLab; 918 919 xform = cmsCreateMultiprofileTransform(Profiles, 2, InputFormat, TYPE_Lab_DBL, Intent, 0); 920 cmsCloseProfile(hLab); 921 922 if (xform == NULL) { 923 924 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Profile -> Lab"); 925 return 0; 926 } 927 928 // Only 1, 3 and 4 channels are allowed 929 930 switch (nChannels) { 931 932 case 1: { 933 cmsToneCurve* Gray2Y = ExtractGray2Y(m ->ContextID, hProfile, Intent); 934 EmitCIEBasedA(m, Gray2Y, &BlackPointAdaptedToD50); 935 cmsFreeToneCurve(Gray2Y); 936 } 937 break; 938 939 case 3: 940 case 4: { 941 cmsUInt32Number OutFrm = TYPE_Lab_16; 942 cmsPipeline* DeviceLink; 943 _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform; 944 945 DeviceLink = cmsPipelineDup(v ->Lut); 946 if (DeviceLink == NULL) return 0; 947 948 dwFlags |= cmsFLAGS_FORCE_CLUT; 949 _cmsOptimizePipeline(&DeviceLink, Intent, &InputFormat, &OutFrm, &dwFlags); 950 951 rc = EmitCIEBasedDEF(m, DeviceLink, Intent, &BlackPointAdaptedToD50); 952 cmsPipelineFree(DeviceLink); 953 } 954 break; 955 956 default: 957 958 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Only 3, 4 channels supported for CSA. This profile has %d channels.", nChannels); 959 return 0; 960 } 961 962 963 cmsDeleteTransform(xform); 964 965 return 1; 966 } 967 968 static 969 cmsFloat64Number* GetPtrToMatrix(const cmsStage* mpe) 970 { 971 _cmsStageMatrixData* Data = (_cmsStageMatrixData*) mpe ->Data; 972 973 return Data -> Double; 974 } 975 976 977 // Does create CSA based on matrix-shaper. Allowed types are gray and RGB based 978 979 static 980 int WriteInputMatrixShaper(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsStage* Matrix, cmsStage* Shaper) 981 { 982 cmsColorSpaceSignature ColorSpace; 983 int rc; 984 cmsCIEXYZ BlackPointAdaptedToD50; 985 986 ColorSpace = cmsGetColorSpace(hProfile); 987 988 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0); 989 990 if (ColorSpace == cmsSigGrayData) { 991 992 cmsToneCurve** ShaperCurve = _cmsStageGetPtrToCurveSet(Shaper); 993 rc = EmitCIEBasedA(m, ShaperCurve[0], &BlackPointAdaptedToD50); 994 995 } 996 else 997 if (ColorSpace == cmsSigRgbData) { 998 999 cmsMAT3 Mat; 1000 int i, j; 1001 1002 memmove(&Mat, GetPtrToMatrix(Matrix), sizeof(Mat)); 1003 1004 for (i=0; i < 3; i++) 1005 for (j=0; j < 3; j++) 1006 Mat.v[i].n[j] *= MAX_ENCODEABLE_XYZ; 1007 1008 rc = EmitCIEBasedABC(m, (cmsFloat64Number *) &Mat, 1009 _cmsStageGetPtrToCurveSet(Shaper), 1010 &BlackPointAdaptedToD50); 1011 } 1012 else { 1013 1014 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Profile is not suitable for CSA. Unsupported colorspace."); 1015 return 0; 1016 } 1017 1018 return rc; 1019 } 1020 1021 1022 1023 // Creates a PostScript color list from a named profile data. 1024 // This is a HP extension, and it works in Lab instead of XYZ 1025 1026 static 1027 int WriteNamedColorCSA(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, int Intent) 1028 { 1029 cmsHTRANSFORM xform; 1030 cmsHPROFILE hLab; 1031 int i, nColors; 1032 char ColorName[32]; 1033 cmsNAMEDCOLORLIST* NamedColorList; 1034 1035 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL); 1036 xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, hLab, TYPE_Lab_DBL, Intent, 0); 1037 if (xform == NULL) return 0; 1038 1039 NamedColorList = cmsGetNamedColorList(xform); 1040 if (NamedColorList == NULL) return 0; 1041 1042 _cmsIOPrintf(m, "<<\n"); 1043 _cmsIOPrintf(m, "(colorlistcomment) (%s)\n", "Named color CSA"); 1044 _cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n"); 1045 _cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n"); 1046 1047 nColors = cmsNamedColorCount(NamedColorList); 1048 1049 1050 for (i=0; i < nColors; i++) { 1051 1052 cmsUInt16Number In[1]; 1053 cmsCIELab Lab; 1054 1055 In[0] = (cmsUInt16Number) i; 1056 1057 if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL)) 1058 continue; 1059 1060 cmsDoTransform(xform, In, &Lab, 1); 1061 _cmsIOPrintf(m, " (%s) [ %.3f %.3f %.3f ]\n", ColorName, Lab.L, Lab.a, Lab.b); 1062 } 1063 1064 1065 1066 _cmsIOPrintf(m, ">>\n"); 1067 1068 cmsDeleteTransform(xform); 1069 cmsCloseProfile(hLab); 1070 return 1; 1071 } 1072 1073 1074 // Does create a Color Space Array on XYZ colorspace for PostScript usage 1075 static 1076 cmsUInt32Number GenerateCSA(cmsContext ContextID, 1077 cmsHPROFILE hProfile, 1078 cmsUInt32Number Intent, 1079 cmsUInt32Number dwFlags, 1080 cmsIOHANDLER* mem) 1081 { 1082 cmsUInt32Number dwBytesUsed; 1083 cmsPipeline* lut = NULL; 1084 cmsStage* Matrix, *Shaper; 1085 1086 1087 // Is a named color profile? 1088 if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) { 1089 1090 if (!WriteNamedColorCSA(mem, hProfile, Intent)) goto Error; 1091 } 1092 else { 1093 1094 1095 // Any profile class are allowed (including devicelink), but 1096 // output (PCS) colorspace must be XYZ or Lab 1097 cmsColorSpaceSignature ColorSpace = cmsGetPCS(hProfile); 1098 1099 if (ColorSpace != cmsSigXYZData && 1100 ColorSpace != cmsSigLabData) { 1101 1102 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Invalid output color space"); 1103 goto Error; 1104 } 1105 1106 1107 // Read the lut with all necessary conversion stages 1108 lut = _cmsReadInputLUT(hProfile, Intent); 1109 if (lut == NULL) goto Error; 1110 1111 1112 // Tone curves + matrix can be implemented without any LUT 1113 if (cmsPipelineCheckAndRetreiveStages(lut, 2, cmsSigCurveSetElemType, cmsSigMatrixElemType, &Shaper, &Matrix)) { 1114 1115 if (!WriteInputMatrixShaper(mem, hProfile, Matrix, Shaper)) goto Error; 1116 1117 } 1118 else { 1119 // We need a LUT for the rest 1120 if (!WriteInputLUT(mem, hProfile, Intent, dwFlags)) goto Error; 1121 } 1122 } 1123 1124 1125 // Done, keep memory usage 1126 dwBytesUsed = mem ->UsedSpace; 1127 1128 // Get rid of LUT 1129 if (lut != NULL) cmsPipelineFree(lut); 1130 1131 // Finally, return used byte count 1132 return dwBytesUsed; 1133 1134 Error: 1135 if (lut != NULL) cmsPipelineFree(lut); 1136 return 0; 1137 } 1138 1139 // ------------------------------------------------------ Color Rendering Dictionary (CRD) 1140 1141 1142 1143 /* 1144 1145 Black point compensation plus chromatic adaptation: 1146 1147 Step 1 - Chromatic adaptation 1148 ============================= 1149 1150 WPout 1151 X = ------- PQR 1152 Wpin 1153 1154 Step 2 - Black point compensation 1155 ================================= 1156 1157 (WPout - BPout)*X - WPout*(BPin - BPout) 1158 out = --------------------------------------- 1159 WPout - BPin 1160 1161 1162 Algorithm discussion 1163 ==================== 1164 1165 TransformPQR(WPin, BPin, WPout, BPout, PQR) 1166 1167 Wpin,etc= { Xws Yws Zws Pws Qws Rws } 1168 1169 1170 Algorithm Stack 0...n 1171 =========================================================== 1172 PQR BPout WPout BPin WPin 1173 4 index 3 get WPin PQR BPout WPout BPin WPin 1174 div (PQR/WPin) BPout WPout BPin WPin 1175 2 index 3 get WPout (PQR/WPin) BPout WPout BPin WPin 1176 mult WPout*(PQR/WPin) BPout WPout BPin WPin 1177 1178 2 index 3 get WPout WPout*(PQR/WPin) BPout WPout BPin WPin 1179 2 index 3 get BPout WPout WPout*(PQR/WPin) BPout WPout BPin WPin 1180 sub (WPout-BPout) WPout*(PQR/WPin) BPout WPout BPin WPin 1181 mult (WPout-BPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1182 1183 2 index 3 get WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1184 4 index 3 get BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1185 3 index 3 get BPout BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1186 1187 sub (BPin-BPout) WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1188 mult (BPin-BPout)*WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1189 sub (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin 1190 1191 3 index 3 get BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin 1192 3 index 3 get WPout BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin 1193 exch 1194 sub (WPout-BPin) (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin 1195 div 1196 1197 exch pop 1198 exch pop 1199 exch pop 1200 exch pop 1201 1202 */ 1203 1204 1205 static 1206 void EmitPQRStage(cmsIOHANDLER* m, cmsHPROFILE hProfile, int DoBPC, int lIsAbsolute) 1207 { 1208 1209 1210 if (lIsAbsolute) { 1211 1212 // For absolute colorimetric intent, encode back to relative 1213 // and generate a relative Pipeline 1214 1215 // Relative encoding is obtained across XYZpcs*(D50/WhitePoint) 1216 1217 cmsCIEXYZ White; 1218 1219 _cmsReadMediaWhitePoint(&White, hProfile); 1220 1221 _cmsIOPrintf(m,"/MatrixPQR [1 0 0 0 1 0 0 0 1 ]\n"); 1222 _cmsIOPrintf(m,"/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n"); 1223 1224 _cmsIOPrintf(m, "%% Absolute colorimetric -- encode to relative to maximize LUT usage\n" 1225 "/TransformPQR [\n" 1226 "{0.9642 mul %g div exch pop exch pop exch pop exch pop} bind\n" 1227 "{1.0000 mul %g div exch pop exch pop exch pop exch pop} bind\n" 1228 "{0.8249 mul %g div exch pop exch pop exch pop exch pop} bind\n]\n", 1229 White.X, White.Y, White.Z); 1230 return; 1231 } 1232 1233 1234 _cmsIOPrintf(m,"%% Bradford Cone Space\n" 1235 "/MatrixPQR [0.8951 -0.7502 0.0389 0.2664 1.7135 -0.0685 -0.1614 0.0367 1.0296 ] \n"); 1236 1237 _cmsIOPrintf(m, "/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n"); 1238 1239 1240 // No BPC 1241 1242 if (!DoBPC) { 1243 1244 _cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space\n" 1245 "/TransformPQR [\n" 1246 "{exch pop exch 3 get mul exch pop exch 3 get div} bind\n" 1247 "{exch pop exch 4 get mul exch pop exch 4 get div} bind\n" 1248 "{exch pop exch 5 get mul exch pop exch 5 get div} bind\n]\n"); 1249 } else { 1250 1251 // BPC 1252 1253 _cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space plus BPC\n" 1254 "/TransformPQR [\n"); 1255 1256 _cmsIOPrintf(m, "{4 index 3 get div 2 index 3 get mul " 1257 "2 index 3 get 2 index 3 get sub mul " 1258 "2 index 3 get 4 index 3 get 3 index 3 get sub mul sub " 1259 "3 index 3 get 3 index 3 get exch sub div " 1260 "exch pop exch pop exch pop exch pop } bind\n"); 1261 1262 _cmsIOPrintf(m, "{4 index 4 get div 2 index 4 get mul " 1263 "2 index 4 get 2 index 4 get sub mul " 1264 "2 index 4 get 4 index 4 get 3 index 4 get sub mul sub " 1265 "3 index 4 get 3 index 4 get exch sub div " 1266 "exch pop exch pop exch pop exch pop } bind\n"); 1267 1268 _cmsIOPrintf(m, "{4 index 5 get div 2 index 5 get mul " 1269 "2 index 5 get 2 index 5 get sub mul " 1270 "2 index 5 get 4 index 5 get 3 index 5 get sub mul sub " 1271 "3 index 5 get 3 index 5 get exch sub div " 1272 "exch pop exch pop exch pop exch pop } bind\n]\n"); 1273 1274 } 1275 1276 1277 } 1278 1279 1280 static 1281 void EmitXYZ2Lab(cmsIOHANDLER* m) 1282 { 1283 _cmsIOPrintf(m, "/RangeLMN [ -0.635 2.0 0 2 -0.635 2.0 ]\n"); 1284 _cmsIOPrintf(m, "/EncodeLMN [\n"); 1285 _cmsIOPrintf(m, "{ 0.964200 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n"); 1286 _cmsIOPrintf(m, "{ 1.000000 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n"); 1287 _cmsIOPrintf(m, "{ 0.824900 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n"); 1288 _cmsIOPrintf(m, "]\n"); 1289 _cmsIOPrintf(m, "/MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]\n"); 1290 _cmsIOPrintf(m, "/EncodeABC [\n"); 1291 1292 1293 _cmsIOPrintf(m, "{ 116 mul 16 sub 100 div } bind\n"); 1294 _cmsIOPrintf(m, "{ 500 mul 128 add 256 div } bind\n"); 1295 _cmsIOPrintf(m, "{ 200 mul 128 add 256 div } bind\n"); 1296 1297 1298 _cmsIOPrintf(m, "]\n"); 1299 1300 1301 } 1302 1303 // Due to impedance mismatch between XYZ and almost all RGB and CMYK spaces 1304 // I choose to dump LUTS in Lab instead of XYZ. There is still a lot of wasted 1305 // space on 3D CLUT, but since space seems not to be a problem here, 33 points 1306 // would give a reasonable accurancy. Note also that CRD tables must operate in 1307 // 8 bits. 1308 1309 static 1310 int WriteOutputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, int Intent, cmsUInt32Number dwFlags) 1311 { 1312 cmsHPROFILE hLab; 1313 cmsHTRANSFORM xform; 1314 int i, nChannels; 1315 cmsUInt32Number OutputFormat; 1316 _cmsTRANSFORM* v; 1317 cmsPipeline* DeviceLink; 1318 cmsHPROFILE Profiles[3]; 1319 cmsCIEXYZ BlackPointAdaptedToD50; 1320 cmsBool lDoBPC = (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION); 1321 cmsBool lFixWhite = !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP); 1322 cmsUInt32Number InFrm = TYPE_Lab_16; 1323 int RelativeEncodingIntent; 1324 cmsColorSpaceSignature ColorSpace; 1325 1326 1327 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL); 1328 if (hLab == NULL) return 0; 1329 1330 OutputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE); 1331 nChannels = T_CHANNELS(OutputFormat); 1332 1333 ColorSpace = cmsGetColorSpace(hProfile); 1334 1335 // For absolute colorimetric, the LUT is encoded as relative in order to preserve precision. 1336 1337 RelativeEncodingIntent = Intent; 1338 if (RelativeEncodingIntent == INTENT_ABSOLUTE_COLORIMETRIC) 1339 RelativeEncodingIntent = INTENT_RELATIVE_COLORIMETRIC; 1340 1341 1342 // Use V4 Lab always 1343 Profiles[0] = hLab; 1344 Profiles[1] = hProfile; 1345 1346 xform = cmsCreateMultiprofileTransformTHR(m ->ContextID, 1347 Profiles, 2, TYPE_Lab_DBL, 1348 OutputFormat, RelativeEncodingIntent, 0); 1349 cmsCloseProfile(hLab); 1350 1351 if (xform == NULL) { 1352 1353 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Lab -> Profile in CRD creation"); 1354 return 0; 1355 } 1356 1357 // Get a copy of the internal devicelink 1358 v = (_cmsTRANSFORM*) xform; 1359 DeviceLink = cmsPipelineDup(v ->Lut); 1360 if (DeviceLink == NULL) return 0; 1361 1362 1363 // We need a CLUT 1364 dwFlags |= cmsFLAGS_FORCE_CLUT; 1365 _cmsOptimizePipeline(&DeviceLink, RelativeEncodingIntent, &InFrm, &OutputFormat, &dwFlags); 1366 1367 _cmsIOPrintf(m, "<<\n"); 1368 _cmsIOPrintf(m, "/ColorRenderingType 1\n"); 1369 1370 1371 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0); 1372 1373 // Emit headers, etc. 1374 EmitWhiteBlackD50(m, &BlackPointAdaptedToD50); 1375 EmitPQRStage(m, hProfile, lDoBPC, Intent == INTENT_ABSOLUTE_COLORIMETRIC); 1376 EmitXYZ2Lab(m); 1377 1378 1379 // FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab 1380 // does map a=b=0 not falling into any specific node. Since range a,b goes -128..127, 1381 // zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to 1382 // zero. This would sacrifice a bit of highlights, but failure to do so would cause 1383 // scum dot. Ouch. 1384 1385 if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) 1386 lFixWhite = FALSE; 1387 1388 _cmsIOPrintf(m, "/RenderTable "); 1389 1390 1391 WriteCLUT(m, cmsPipelineGetPtrToFirstStage(DeviceLink), "<", ">\n", "", "", lFixWhite, ColorSpace); 1392 1393 _cmsIOPrintf(m, " %d {} bind ", nChannels); 1394 1395 for (i=1; i < nChannels; i++) 1396 _cmsIOPrintf(m, "dup "); 1397 1398 _cmsIOPrintf(m, "]\n"); 1399 1400 1401 EmitIntent(m, Intent); 1402 1403 _cmsIOPrintf(m, ">>\n"); 1404 1405 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { 1406 1407 _cmsIOPrintf(m, "/Current exch /ColorRendering defineresource pop\n"); 1408 } 1409 1410 cmsPipelineFree(DeviceLink); 1411 cmsDeleteTransform(xform); 1412 1413 return 1; 1414 } 1415 1416 1417 // Builds a ASCII string containing colorant list in 0..1.0 range 1418 static 1419 void BuildColorantList(char *Colorant, int nColorant, cmsUInt16Number Out[]) 1420 { 1421 char Buff[32]; 1422 int j; 1423 1424 Colorant[0] = 0; 1425 if (nColorant > cmsMAXCHANNELS) 1426 nColorant = cmsMAXCHANNELS; 1427 1428 for (j=0; j < nColorant; j++) { 1429 1430 sprintf(Buff, "%.3f", Out[j] / 65535.0); 1431 strcat(Colorant, Buff); 1432 if (j < nColorant -1) 1433 strcat(Colorant, " "); 1434 1435 } 1436 } 1437 1438 1439 // Creates a PostScript color list from a named profile data. 1440 // This is a HP extension. 1441 1442 static 1443 int WriteNamedColorCRD(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, int Intent, cmsUInt32Number dwFlags) 1444 { 1445 cmsHTRANSFORM xform; 1446 int i, nColors, nColorant; 1447 cmsUInt32Number OutputFormat; 1448 char ColorName[32]; 1449 char Colorant[128]; 1450 cmsNAMEDCOLORLIST* NamedColorList; 1451 1452 1453 OutputFormat = cmsFormatterForColorspaceOfProfile(hNamedColor, 2, FALSE); 1454 nColorant = T_CHANNELS(OutputFormat); 1455 1456 1457 xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, NULL, OutputFormat, Intent, dwFlags); 1458 if (xform == NULL) return 0; 1459 1460 1461 NamedColorList = cmsGetNamedColorList(xform); 1462 if (NamedColorList == NULL) return 0; 1463 1464 _cmsIOPrintf(m, "<<\n"); 1465 _cmsIOPrintf(m, "(colorlistcomment) (%s) \n", "Named profile"); 1466 _cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n"); 1467 _cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n"); 1468 1469 nColors = cmsNamedColorCount(NamedColorList); 1470 1471 for (i=0; i < nColors; i++) { 1472 1473 cmsUInt16Number In[1]; 1474 cmsUInt16Number Out[cmsMAXCHANNELS]; 1475 1476 In[0] = (cmsUInt16Number) i; 1477 1478 if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL)) 1479 continue; 1480 1481 cmsDoTransform(xform, In, Out, 1); 1482 BuildColorantList(Colorant, nColorant, Out); 1483 _cmsIOPrintf(m, " (%s) [ %s ]\n", ColorName, Colorant); 1484 } 1485 1486 _cmsIOPrintf(m, " >>"); 1487 1488 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { 1489 1490 _cmsIOPrintf(m, " /Current exch /HPSpotTable defineresource pop\n"); 1491 } 1492 1493 cmsDeleteTransform(xform); 1494 return 1; 1495 } 1496 1497 1498 1499 // This one does create a Color Rendering Dictionary. 1500 // CRD are always LUT-Based, no matter if profile is 1501 // implemented as matrix-shaper. 1502 1503 static 1504 cmsUInt32Number GenerateCRD(cmsContext ContextID, 1505 cmsHPROFILE hProfile, 1506 cmsUInt32Number Intent, cmsUInt32Number dwFlags, 1507 cmsIOHANDLER* mem) 1508 { 1509 cmsUInt32Number dwBytesUsed; 1510 1511 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { 1512 1513 EmitHeader(mem, "Color Rendering Dictionary (CRD)", hProfile); 1514 } 1515 1516 1517 // Is a named color profile? 1518 if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) { 1519 1520 if (!WriteNamedColorCRD(mem, hProfile, Intent, dwFlags)) { 1521 return 0; 1522 } 1523 } 1524 else { 1525 1526 // CRD are always implemented as LUT 1527 1528 if (!WriteOutputLUT(mem, hProfile, Intent, dwFlags)) { 1529 return 0; 1530 } 1531 } 1532 1533 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { 1534 1535 _cmsIOPrintf(mem, "%%%%EndResource\n"); 1536 _cmsIOPrintf(mem, "\n%% CRD End\n"); 1537 } 1538 1539 // Done, keep memory usage 1540 dwBytesUsed = mem ->UsedSpace; 1541 1542 // Finally, return used byte count 1543 return dwBytesUsed; 1544 1545 cmsUNUSED_PARAMETER(ContextID); 1546 } 1547 1548 1549 1550 1551 cmsUInt32Number CMSEXPORT cmsGetPostScriptColorResource(cmsContext ContextID, 1552 cmsPSResourceType Type, 1553 cmsHPROFILE hProfile, 1554 cmsUInt32Number Intent, 1555 cmsUInt32Number dwFlags, 1556 cmsIOHANDLER* io) 1557 { 1558 cmsUInt32Number rc; 1559 1560 1561 switch (Type) { 1562 1563 case cmsPS_RESOURCE_CSA: 1564 rc = GenerateCSA(ContextID, hProfile, Intent, dwFlags, io); 1565 break; 1566 1567 default: 1568 case cmsPS_RESOURCE_CRD: 1569 rc = GenerateCRD(ContextID, hProfile, Intent, dwFlags, io); 1570 break; 1571 } 1572 1573 return rc; 1574 } 1575 1576 1577 1578 cmsUInt32Number CMSEXPORT cmsGetPostScriptCRD(cmsContext ContextID, 1579 cmsHPROFILE hProfile, 1580 cmsUInt32Number Intent, cmsUInt32Number dwFlags, 1581 void* Buffer, cmsUInt32Number dwBufferLen) 1582 { 1583 cmsIOHANDLER* mem; 1584 cmsUInt32Number dwBytesUsed; 1585 1586 // Set up the serialization engine 1587 if (Buffer == NULL) 1588 mem = cmsOpenIOhandlerFromNULL(ContextID); 1589 else 1590 mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w"); 1591 1592 if (!mem) return 0; 1593 1594 dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CRD, hProfile, Intent, dwFlags, mem); 1595 1596 // Get rid of memory stream 1597 cmsCloseIOhandler(mem); 1598 1599 return dwBytesUsed; 1600 } 1601 1602 1603 1604 // Does create a Color Space Array on XYZ colorspace for PostScript usage 1605 cmsUInt32Number CMSEXPORT cmsGetPostScriptCSA(cmsContext ContextID, 1606 cmsHPROFILE hProfile, 1607 cmsUInt32Number Intent, 1608 cmsUInt32Number dwFlags, 1609 void* Buffer, 1610 cmsUInt32Number dwBufferLen) 1611 { 1612 cmsIOHANDLER* mem; 1613 cmsUInt32Number dwBytesUsed; 1614 1615 if (Buffer == NULL) 1616 mem = cmsOpenIOhandlerFromNULL(ContextID); 1617 else 1618 mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w"); 1619 1620 if (!mem) return 0; 1621 1622 dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CSA, hProfile, Intent, dwFlags, mem); 1623 1624 // Get rid of memory stream 1625 cmsCloseIOhandler(mem); 1626 1627 return dwBytesUsed; 1628 1629 }