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-2017 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, cmsUInt32Number 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, cmsUInt32Number 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, cmsUInt32Number n, cmsToneCurve* g[]) 576 { 577 cmsUInt32Number 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 beginning 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, cmsUInt32Number 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, cmsUInt32Number 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 && xform != 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 if (xform) cmsDeleteTransform(xform); 880 if (hXYZ) 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, cmsUInt32Number 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 static 976 int WriteInputMatrixShaper(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsStage* Matrix, cmsStage* Shaper) 977 { 978 cmsColorSpaceSignature ColorSpace; 979 int rc; 980 cmsCIEXYZ BlackPointAdaptedToD50; 981 982 ColorSpace = cmsGetColorSpace(hProfile); 983 984 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0); 985 986 if (ColorSpace == cmsSigGrayData) { 987 988 cmsToneCurve** ShaperCurve = _cmsStageGetPtrToCurveSet(Shaper); 989 rc = EmitCIEBasedA(m, ShaperCurve[0], &BlackPointAdaptedToD50); 990 991 } 992 else 993 if (ColorSpace == cmsSigRgbData) { 994 995 cmsMAT3 Mat; 996 int i, j; 997 998 memmove(&Mat, GetPtrToMatrix(Matrix), sizeof(Mat)); 999 1000 for (i = 0; i < 3; i++) 1001 for (j = 0; j < 3; j++) 1002 Mat.v[i].n[j] *= MAX_ENCODEABLE_XYZ; 1003 1004 rc = EmitCIEBasedABC(m, (cmsFloat64Number *)&Mat, 1005 _cmsStageGetPtrToCurveSet(Shaper), 1006 &BlackPointAdaptedToD50); 1007 } 1008 else { 1009 1010 cmsSignalError(m->ContextID, cmsERROR_COLORSPACE_CHECK, "Profile is not suitable for CSA. Unsupported colorspace."); 1011 return 0; 1012 } 1013 1014 return rc; 1015 } 1016 1017 1018 1019 // Creates a PostScript color list from a named profile data. 1020 // This is a HP extension, and it works in Lab instead of XYZ 1021 1022 static 1023 int WriteNamedColorCSA(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent) 1024 { 1025 cmsHTRANSFORM xform; 1026 cmsHPROFILE hLab; 1027 cmsUInt32Number i, nColors; 1028 char ColorName[cmsMAX_PATH]; 1029 cmsNAMEDCOLORLIST* NamedColorList; 1030 1031 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL); 1032 xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, hLab, TYPE_Lab_DBL, Intent, 0); 1033 if (xform == NULL) return 0; 1034 1035 NamedColorList = cmsGetNamedColorList(xform); 1036 if (NamedColorList == NULL) return 0; 1037 1038 _cmsIOPrintf(m, "<<\n"); 1039 _cmsIOPrintf(m, "(colorlistcomment) (%s)\n", "Named color CSA"); 1040 _cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n"); 1041 _cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n"); 1042 1043 nColors = cmsNamedColorCount(NamedColorList); 1044 1045 1046 for (i=0; i < nColors; i++) { 1047 1048 cmsUInt16Number In[1]; 1049 cmsCIELab Lab; 1050 1051 In[0] = (cmsUInt16Number) i; 1052 1053 if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL)) 1054 continue; 1055 1056 cmsDoTransform(xform, In, &Lab, 1); 1057 _cmsIOPrintf(m, " (%s) [ %.3f %.3f %.3f ]\n", ColorName, Lab.L, Lab.a, Lab.b); 1058 } 1059 1060 1061 1062 _cmsIOPrintf(m, ">>\n"); 1063 1064 cmsDeleteTransform(xform); 1065 cmsCloseProfile(hLab); 1066 return 1; 1067 } 1068 1069 1070 // Does create a Color Space Array on XYZ colorspace for PostScript usage 1071 static 1072 cmsUInt32Number GenerateCSA(cmsContext ContextID, 1073 cmsHPROFILE hProfile, 1074 cmsUInt32Number Intent, 1075 cmsUInt32Number dwFlags, 1076 cmsIOHANDLER* mem) 1077 { 1078 cmsUInt32Number dwBytesUsed; 1079 cmsPipeline* lut = NULL; 1080 cmsStage* Matrix, *Shaper; 1081 1082 1083 // Is a named color profile? 1084 if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) { 1085 1086 if (!WriteNamedColorCSA(mem, hProfile, Intent)) goto Error; 1087 } 1088 else { 1089 1090 1091 // Any profile class are allowed (including devicelink), but 1092 // output (PCS) colorspace must be XYZ or Lab 1093 cmsColorSpaceSignature ColorSpace = cmsGetPCS(hProfile); 1094 1095 if (ColorSpace != cmsSigXYZData && 1096 ColorSpace != cmsSigLabData) { 1097 1098 cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Invalid output color space"); 1099 goto Error; 1100 } 1101 1102 1103 // Read the lut with all necessary conversion stages 1104 lut = _cmsReadInputLUT(hProfile, Intent); 1105 if (lut == NULL) goto Error; 1106 1107 1108 // Tone curves + matrix can be implemented without any LUT 1109 if (cmsPipelineCheckAndRetreiveStages(lut, 2, cmsSigCurveSetElemType, cmsSigMatrixElemType, &Shaper, &Matrix)) { 1110 1111 if (!WriteInputMatrixShaper(mem, hProfile, Matrix, Shaper)) goto Error; 1112 1113 } 1114 else { 1115 // We need a LUT for the rest 1116 if (!WriteInputLUT(mem, hProfile, Intent, dwFlags)) goto Error; 1117 } 1118 } 1119 1120 1121 // Done, keep memory usage 1122 dwBytesUsed = mem ->UsedSpace; 1123 1124 // Get rid of LUT 1125 if (lut != NULL) cmsPipelineFree(lut); 1126 1127 // Finally, return used byte count 1128 return dwBytesUsed; 1129 1130 Error: 1131 if (lut != NULL) cmsPipelineFree(lut); 1132 return 0; 1133 } 1134 1135 // ------------------------------------------------------ Color Rendering Dictionary (CRD) 1136 1137 1138 1139 /* 1140 1141 Black point compensation plus chromatic adaptation: 1142 1143 Step 1 - Chromatic adaptation 1144 ============================= 1145 1146 WPout 1147 X = ------- PQR 1148 Wpin 1149 1150 Step 2 - Black point compensation 1151 ================================= 1152 1153 (WPout - BPout)*X - WPout*(BPin - BPout) 1154 out = --------------------------------------- 1155 WPout - BPin 1156 1157 1158 Algorithm discussion 1159 ==================== 1160 1161 TransformPQR(WPin, BPin, WPout, BPout, PQR) 1162 1163 Wpin,etc= { Xws Yws Zws Pws Qws Rws } 1164 1165 1166 Algorithm Stack 0...n 1167 =========================================================== 1168 PQR BPout WPout BPin WPin 1169 4 index 3 get WPin PQR BPout WPout BPin WPin 1170 div (PQR/WPin) BPout WPout BPin WPin 1171 2 index 3 get WPout (PQR/WPin) BPout WPout BPin WPin 1172 mult WPout*(PQR/WPin) BPout WPout BPin WPin 1173 1174 2 index 3 get WPout WPout*(PQR/WPin) BPout WPout BPin WPin 1175 2 index 3 get BPout WPout WPout*(PQR/WPin) BPout WPout BPin WPin 1176 sub (WPout-BPout) WPout*(PQR/WPin) BPout WPout BPin WPin 1177 mult (WPout-BPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1178 1179 2 index 3 get WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1180 4 index 3 get BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1181 3 index 3 get BPout BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1182 1183 sub (BPin-BPout) WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1184 mult (BPin-BPout)*WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin 1185 sub (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin 1186 1187 3 index 3 get BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin 1188 3 index 3 get WPout BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin 1189 exch 1190 sub (WPout-BPin) (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin 1191 div 1192 1193 exch pop 1194 exch pop 1195 exch pop 1196 exch pop 1197 1198 */ 1199 1200 1201 static 1202 void EmitPQRStage(cmsIOHANDLER* m, cmsHPROFILE hProfile, int DoBPC, int lIsAbsolute) 1203 { 1204 1205 1206 if (lIsAbsolute) { 1207 1208 // For absolute colorimetric intent, encode back to relative 1209 // and generate a relative Pipeline 1210 1211 // Relative encoding is obtained across XYZpcs*(D50/WhitePoint) 1212 1213 cmsCIEXYZ White; 1214 1215 _cmsReadMediaWhitePoint(&White, hProfile); 1216 1217 _cmsIOPrintf(m,"/MatrixPQR [1 0 0 0 1 0 0 0 1 ]\n"); 1218 _cmsIOPrintf(m,"/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n"); 1219 1220 _cmsIOPrintf(m, "%% Absolute colorimetric -- encode to relative to maximize LUT usage\n" 1221 "/TransformPQR [\n" 1222 "{0.9642 mul %g div exch pop exch pop exch pop exch pop} bind\n" 1223 "{1.0000 mul %g div exch pop exch pop exch pop exch pop} bind\n" 1224 "{0.8249 mul %g div exch pop exch pop exch pop exch pop} bind\n]\n", 1225 White.X, White.Y, White.Z); 1226 return; 1227 } 1228 1229 1230 _cmsIOPrintf(m,"%% Bradford Cone Space\n" 1231 "/MatrixPQR [0.8951 -0.7502 0.0389 0.2664 1.7135 -0.0685 -0.1614 0.0367 1.0296 ] \n"); 1232 1233 _cmsIOPrintf(m, "/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n"); 1234 1235 1236 // No BPC 1237 1238 if (!DoBPC) { 1239 1240 _cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space\n" 1241 "/TransformPQR [\n" 1242 "{exch pop exch 3 get mul exch pop exch 3 get div} bind\n" 1243 "{exch pop exch 4 get mul exch pop exch 4 get div} bind\n" 1244 "{exch pop exch 5 get mul exch pop exch 5 get div} bind\n]\n"); 1245 } else { 1246 1247 // BPC 1248 1249 _cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space plus BPC\n" 1250 "/TransformPQR [\n"); 1251 1252 _cmsIOPrintf(m, "{4 index 3 get div 2 index 3 get mul " 1253 "2 index 3 get 2 index 3 get sub mul " 1254 "2 index 3 get 4 index 3 get 3 index 3 get sub mul sub " 1255 "3 index 3 get 3 index 3 get exch sub div " 1256 "exch pop exch pop exch pop exch pop } bind\n"); 1257 1258 _cmsIOPrintf(m, "{4 index 4 get div 2 index 4 get mul " 1259 "2 index 4 get 2 index 4 get sub mul " 1260 "2 index 4 get 4 index 4 get 3 index 4 get sub mul sub " 1261 "3 index 4 get 3 index 4 get exch sub div " 1262 "exch pop exch pop exch pop exch pop } bind\n"); 1263 1264 _cmsIOPrintf(m, "{4 index 5 get div 2 index 5 get mul " 1265 "2 index 5 get 2 index 5 get sub mul " 1266 "2 index 5 get 4 index 5 get 3 index 5 get sub mul sub " 1267 "3 index 5 get 3 index 5 get exch sub div " 1268 "exch pop exch pop exch pop exch pop } bind\n]\n"); 1269 1270 } 1271 1272 1273 } 1274 1275 1276 static 1277 void EmitXYZ2Lab(cmsIOHANDLER* m) 1278 { 1279 _cmsIOPrintf(m, "/RangeLMN [ -0.635 2.0 0 2 -0.635 2.0 ]\n"); 1280 _cmsIOPrintf(m, "/EncodeLMN [\n"); 1281 _cmsIOPrintf(m, "{ 0.964200 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n"); 1282 _cmsIOPrintf(m, "{ 1.000000 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n"); 1283 _cmsIOPrintf(m, "{ 0.824900 div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n"); 1284 _cmsIOPrintf(m, "]\n"); 1285 _cmsIOPrintf(m, "/MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]\n"); 1286 _cmsIOPrintf(m, "/EncodeABC [\n"); 1287 1288 1289 _cmsIOPrintf(m, "{ 116 mul 16 sub 100 div } bind\n"); 1290 _cmsIOPrintf(m, "{ 500 mul 128 add 256 div } bind\n"); 1291 _cmsIOPrintf(m, "{ 200 mul 128 add 256 div } bind\n"); 1292 1293 1294 _cmsIOPrintf(m, "]\n"); 1295 1296 1297 } 1298 1299 // Due to impedance mismatch between XYZ and almost all RGB and CMYK spaces 1300 // I choose to dump LUTS in Lab instead of XYZ. There is still a lot of wasted 1301 // space on 3D CLUT, but since space seems not to be a problem here, 33 points 1302 // would give a reasonable accurancy. Note also that CRD tables must operate in 1303 // 8 bits. 1304 1305 static 1306 int WriteOutputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags) 1307 { 1308 cmsHPROFILE hLab; 1309 cmsHTRANSFORM xform; 1310 cmsUInt32Number i, nChannels; 1311 cmsUInt32Number OutputFormat; 1312 _cmsTRANSFORM* v; 1313 cmsPipeline* DeviceLink; 1314 cmsHPROFILE Profiles[3]; 1315 cmsCIEXYZ BlackPointAdaptedToD50; 1316 cmsBool lDoBPC = (cmsBool) (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION); 1317 cmsBool lFixWhite = (cmsBool) !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP); 1318 cmsUInt32Number InFrm = TYPE_Lab_16; 1319 cmsUInt32Number RelativeEncodingIntent; 1320 cmsColorSpaceSignature ColorSpace; 1321 1322 1323 hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL); 1324 if (hLab == NULL) return 0; 1325 1326 OutputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE); 1327 nChannels = T_CHANNELS(OutputFormat); 1328 1329 ColorSpace = cmsGetColorSpace(hProfile); 1330 1331 // For absolute colorimetric, the LUT is encoded as relative in order to preserve precision. 1332 1333 RelativeEncodingIntent = Intent; 1334 if (RelativeEncodingIntent == INTENT_ABSOLUTE_COLORIMETRIC) 1335 RelativeEncodingIntent = INTENT_RELATIVE_COLORIMETRIC; 1336 1337 1338 // Use V4 Lab always 1339 Profiles[0] = hLab; 1340 Profiles[1] = hProfile; 1341 1342 xform = cmsCreateMultiprofileTransformTHR(m ->ContextID, 1343 Profiles, 2, TYPE_Lab_DBL, 1344 OutputFormat, RelativeEncodingIntent, 0); 1345 cmsCloseProfile(hLab); 1346 1347 if (xform == NULL) { 1348 1349 cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Lab -> Profile in CRD creation"); 1350 return 0; 1351 } 1352 1353 // Get a copy of the internal devicelink 1354 v = (_cmsTRANSFORM*) xform; 1355 DeviceLink = cmsPipelineDup(v ->Lut); 1356 if (DeviceLink == NULL) return 0; 1357 1358 1359 // We need a CLUT 1360 dwFlags |= cmsFLAGS_FORCE_CLUT; 1361 _cmsOptimizePipeline(m->ContextID, &DeviceLink, RelativeEncodingIntent, &InFrm, &OutputFormat, &dwFlags); 1362 1363 _cmsIOPrintf(m, "<<\n"); 1364 _cmsIOPrintf(m, "/ColorRenderingType 1\n"); 1365 1366 1367 cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0); 1368 1369 // Emit headers, etc. 1370 EmitWhiteBlackD50(m, &BlackPointAdaptedToD50); 1371 EmitPQRStage(m, hProfile, lDoBPC, Intent == INTENT_ABSOLUTE_COLORIMETRIC); 1372 EmitXYZ2Lab(m); 1373 1374 1375 // FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab 1376 // does map a=b=0 not falling into any specific node. Since range a,b goes -128..127, 1377 // zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to 1378 // zero. This would sacrifice a bit of highlights, but failure to do so would cause 1379 // scum dot. Ouch. 1380 1381 if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) 1382 lFixWhite = FALSE; 1383 1384 _cmsIOPrintf(m, "/RenderTable "); 1385 1386 1387 WriteCLUT(m, cmsPipelineGetPtrToFirstStage(DeviceLink), "<", ">\n", "", "", lFixWhite, ColorSpace); 1388 1389 _cmsIOPrintf(m, " %d {} bind ", nChannels); 1390 1391 for (i=1; i < nChannels; i++) 1392 _cmsIOPrintf(m, "dup "); 1393 1394 _cmsIOPrintf(m, "]\n"); 1395 1396 1397 EmitIntent(m, Intent); 1398 1399 _cmsIOPrintf(m, ">>\n"); 1400 1401 if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) { 1402 1403 _cmsIOPrintf(m, "/Current exch /ColorRendering defineresource pop\n"); 1404 } 1405 1406 cmsPipelineFree(DeviceLink); 1407 cmsDeleteTransform(xform); 1408 1409 return 1; 1410 } 1411 1412 1413 // Builds a ASCII string containing colorant list in 0..1.0 range 1414 static 1415 void BuildColorantList(char *Colorant, cmsUInt32Number nColorant, cmsUInt16Number Out[]) 1416 { 1417 char Buff[32]; 1418 cmsUInt32Number j; 1419 1420 Colorant[0] = 0; 1421 if (nColorant > cmsMAXCHANNELS) 1422 nColorant = cmsMAXCHANNELS; 1423 1424 for (j = 0; j < nColorant; j++) { 1425 1426 snprintf(Buff, 31, "%.3f", Out[j] / 65535.0); 1427 Buff[31] = 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, cmsUInt32Number Intent, cmsUInt32Number dwFlags) 1441 { 1442 cmsHTRANSFORM xform; 1443 cmsUInt32Number i, nColors, nColorant; 1444 cmsUInt32Number OutputFormat; 1445 char ColorName[cmsMAX_PATH]; 1446 char Colorant[512]; 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 // Finally, return used byte count 1540 return dwBytesUsed; 1541 1542 cmsUNUSED_PARAMETER(ContextID); 1543 } 1544 1545 1546 1547 1548 cmsUInt32Number CMSEXPORT cmsGetPostScriptColorResource(cmsContext ContextID, 1549 cmsPSResourceType Type, 1550 cmsHPROFILE hProfile, 1551 cmsUInt32Number Intent, 1552 cmsUInt32Number dwFlags, 1553 cmsIOHANDLER* io) 1554 { 1555 cmsUInt32Number rc; 1556 1557 1558 switch (Type) { 1559 1560 case cmsPS_RESOURCE_CSA: 1561 rc = GenerateCSA(ContextID, hProfile, Intent, dwFlags, io); 1562 break; 1563 1564 default: 1565 case cmsPS_RESOURCE_CRD: 1566 rc = GenerateCRD(ContextID, hProfile, Intent, dwFlags, io); 1567 break; 1568 } 1569 1570 return rc; 1571 } 1572 1573 1574 1575 cmsUInt32Number CMSEXPORT cmsGetPostScriptCRD(cmsContext ContextID, 1576 cmsHPROFILE hProfile, 1577 cmsUInt32Number Intent, cmsUInt32Number dwFlags, 1578 void* Buffer, cmsUInt32Number dwBufferLen) 1579 { 1580 cmsIOHANDLER* mem; 1581 cmsUInt32Number dwBytesUsed; 1582 1583 // Set up the serialization engine 1584 if (Buffer == NULL) 1585 mem = cmsOpenIOhandlerFromNULL(ContextID); 1586 else 1587 mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w"); 1588 1589 if (!mem) return 0; 1590 1591 dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CRD, hProfile, Intent, dwFlags, mem); 1592 1593 // Get rid of memory stream 1594 cmsCloseIOhandler(mem); 1595 1596 return dwBytesUsed; 1597 } 1598 1599 1600 1601 // Does create a Color Space Array on XYZ colorspace for PostScript usage 1602 cmsUInt32Number CMSEXPORT cmsGetPostScriptCSA(cmsContext ContextID, 1603 cmsHPROFILE hProfile, 1604 cmsUInt32Number Intent, 1605 cmsUInt32Number dwFlags, 1606 void* Buffer, 1607 cmsUInt32Number dwBufferLen) 1608 { 1609 cmsIOHANDLER* mem; 1610 cmsUInt32Number dwBytesUsed; 1611 1612 if (Buffer == NULL) 1613 mem = cmsOpenIOhandlerFromNULL(ContextID); 1614 else 1615 mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w"); 1616 1617 if (!mem) return 0; 1618 1619 dwBytesUsed = cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CSA, hProfile, Intent, dwFlags, mem); 1620 1621 // Get rid of memory stream 1622 cmsCloseIOhandler(mem); 1623 1624 return dwBytesUsed; 1625 1626 }