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 }