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