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