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