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