//---------------------------------------------------------------------------------
//
//  Little Color Management System
//  Copyright (c) 1998-2020 Marti Maria Saguer
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//---------------------------------------------------------------------------------
//
 
#include "lcms2_internal.h"
 
// PostScript ColorRenderingDictionary and ColorSpaceArray
 
 
#define MAXPSCOLS   60      // Columns on tables
 
/*
    Implementation
    --------------
 
  PostScript does use XYZ as its internal PCS. But since PostScript
  interpolation tables are limited to 8 bits, I use Lab as a way to
  improve the accuracy, favoring perceptual results. So, for the creation
  of each CRD, CSA the profiles are converted to Lab via a device
  link between  profile -> Lab or Lab -> profile. The PS code necessary to
  convert Lab <-> XYZ is also included.
 
 
 
  Color Space Arrays (CSA)
  ==================================================================================
 
  In order to obtain precision, code chooses between three ways to implement
  the device -> XYZ transform. These cases identifies monochrome profiles (often
  implemented as a set of curves), matrix-shaper and Pipeline-based.
 
  Monochrome
  -----------
 
  This is implemented as /CIEBasedA CSA. The prelinearization curve is
  placed into /DecodeA section, and matrix equals to D50. Since here is
  no interpolation tables, I do the conversion directly to XYZ
 
  NOTE: CLUT-based monochrome profiles are NOT supported. So, cmsFLAGS_MATRIXINPUT
  flag is forced on such profiles.
 
    [ /CIEBasedA
      <<
            /DecodeA { transfer function } bind
            /MatrixA [D50]
            /RangeLMN [ 0.0 cmsD50X 0.0 cmsD50Y 0.0 cmsD50Z ]
            /WhitePoint [D50]
            /BlackPoint [BP]
            /RenderingIntent (intent)
      >>
    ]
 
   On simpler profiles, the PCS is already XYZ, so no conversion is required.
 
 
   Matrix-shaper based
   -------------------
 
   This is implemented both with /CIEBasedABC or /CIEBasedDEF depending on the
   profile implementation. Since here there are no interpolation tables, I do
   the conversion directly to XYZ
 
 
 
    [ /CIEBasedABC
            <<
                /DecodeABC [ {transfer1} {transfer2} {transfer3} ]
                /MatrixABC [Matrix]
                /RangeLMN [ 0.0 cmsD50X 0.0 cmsD50Y 0.0 cmsD50Z ]
                /DecodeLMN [ { / 2} dup dup ]
                /WhitePoint [D50]
                /BlackPoint [BP]
                /RenderingIntent (intent)
            >>
    ]
 
 
    CLUT based
    ----------
 
     Lab is used in such cases.
 
    [ /CIEBasedDEF
            <<
            /DecodeDEF [ <prelinearization> ]
            /Table [ p p p [<...>]]
            /RangeABC [ 0 1 0 1 0 1]
            /DecodeABC[ <postlinearization> ]
            /RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]
               % -128/500 1+127/500 0 1  -127/200 1+128/200
            /MatrixABC [ 1 1 1 1 0 0 0 0 -1]
            /WhitePoint [D50]
            /BlackPoint [BP]
            /RenderingIntent (intent)
    ]
 
 
  Color Rendering Dictionaries (CRD)
  ==================================
  These are always implemented as CLUT, and always are using Lab. Since CRD are expected to
  be used as resources, the code adds the definition as well.
 
  <<
    /ColorRenderingType 1
    /WhitePoint [ D50 ]
    /BlackPoint [BP]
    /MatrixPQR [ Bradford ]
    /RangePQR [-0.125 1.375 -0.125 1.375 -0.125 1.375 ]
    /TransformPQR [
    {4 index 3 get div 2 index 3 get mul exch pop exch pop exch pop exch pop } bind
    {4 index 4 get div 2 index 4 get mul exch pop exch pop exch pop exch pop } bind
    {4 index 5 get div 2 index 5 get mul exch pop exch pop exch pop exch pop } bind
    ]
    /MatrixABC <...>
    /EncodeABC <...>
    /RangeABC  <.. used for  XYZ -> Lab>
    /EncodeLMN
    /RenderTable [ p p p [<...>]]
 
    /RenderingIntent (Perceptual)
  >>
  /Current exch /ColorRendering defineresource pop
 
 
  The following stages are used to convert from XYZ to Lab
  --------------------------------------------------------
 
  Input is given at LMN stage on X, Y, Z
 
  Encode LMN gives us f(X/Xn), f(Y/Yn), f(Z/Zn)
 
  /EncodeLMN [
 
    { 0.964200  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
    { 1.000000  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
    { 0.824900  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind
 
    ]
 
 
  MatrixABC is used to compute f(Y/Yn), f(X/Xn) - f(Y/Yn), f(Y/Yn) - f(Z/Zn)
 
  | 0  1  0|
  | 1 -1  0|
  | 0  1 -1|
 
  /MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]
 
 EncodeABC finally gives Lab values.
 
  /EncodeABC [
    { 116 mul  16 sub 100 div  } bind
    { 500 mul 128 add 255 div  } bind
    { 200 mul 128 add 255 div  } bind
    ]
 
  The following stages are used to convert Lab to XYZ
  ----------------------------------------------------
 
    /RangeABC [ 0 1 0 1 0 1]
    /DecodeABC [ { 100 mul 16 add 116 div } bind
                 { 255 mul 128 sub 500 div } bind
                 { 255 mul 128 sub 200 div } bind
               ]
 
    /MatrixABC [ 1 1 1 1 0 0 0 0 -1]
    /DecodeLMN [
                {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.964200 mul} bind
                {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind
                {dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse 0.824900 mul} bind
                ]
 
 
*/
 
/*
 
 PostScript algorithms discussion.
 =========================================================================================================
 
  1D interpolation algorithm
 
 
  1D interpolation (float)
  ------------------------
 
    val2 = Domain * Value;
 
    cell0 = (int) floor(val2);
    cell1 = (int) ceil(val2);
 
    rest = val2 - cell0;
 
    y0 = LutTable[cell0] ;
    y1 = LutTable[cell1] ;
 
    y = y0 + (y1 - y0) * rest;
 
 
 
  PostScript code                   Stack
  ================================================
 
  {                                 % v
    <check 0..1.0>
    [array]                         % v tab
    dup                             % v tab tab
    length 1 sub                    % v tab dom
 
    3 -1 roll                       % tab dom v
 
    mul                             % tab val2
    dup                             % tab val2 val2
    dup                             % tab val2 val2 val2
    floor cvi                       % tab val2 val2 cell0
    exch                            % tab val2 cell0 val2
    ceiling cvi                     % tab val2 cell0 cell1
 
    3 index                         % tab val2 cell0 cell1 tab
    exch                            % tab val2 cell0 tab cell1
    get                             % tab val2 cell0 y1
 
    4 -1 roll                       % val2 cell0 y1 tab
    3 -1 roll                       % val2 y1 tab cell0
    get                             % val2 y1 y0
 
    dup                             % val2 y1 y0 y0
    3 1 roll                        % val2 y0 y1 y0
 
    sub                             % val2 y0 (y1-y0)
    3 -1 roll                       % y0 (y1-y0) val2
    dup                             % y0 (y1-y0) val2 val2
    floor cvi                       % y0 (y1-y0) val2 floor(val2)
    sub                             % y0 (y1-y0) rest
    mul                             % y0 t1
    add                             % y
    65535 div                       % result
 
  } bind
 
 
*/
 
 
// This struct holds the memory block currently being write
typedef struct {
    _cmsStageCLutData* Pipeline;
    cmsIOHANDLER* m;
 
    int FirstComponent;
    int SecondComponent;
 
    const char* PreMaj;
    const char* PostMaj;
    const char* PreMin;
    const char* PostMin;
 
    int  FixWhite;    // Force mapping of pure white
 
    cmsColorSpaceSignature  ColorSpace;  // ColorSpace of profile
 
 
} cmsPsSamplerCargo;
 
static int _cmsPSActualColumn = 0;
 
 
// Convert to byte
static
cmsUInt8Number Word2Byte(cmsUInt16Number w)
{
    return (cmsUInt8Number) floor((cmsFloat64Number) w / 257.0 + 0.5);
}
 
 
// Write a cooked byte
static
void WriteByte(cmsIOHANDLER* m, cmsUInt8Number b)
{
    _cmsIOPrintf(m, "%02x", b);
    _cmsPSActualColumn += 2;
 
    if (_cmsPSActualColumn > MAXPSCOLS) {
 
        _cmsIOPrintf(m, "\n");
        _cmsPSActualColumn = 0;
    }
}
 
// ----------------------------------------------------------------- PostScript generation
 
 
// Removes offending carriage returns
 
static
char* RemoveCR(const char* txt)
{
    static char Buffer[2048];
    char* pt;
 
    strncpy(Buffer, txt, 2047);
    Buffer[2047] = 0;
    for (pt = Buffer; *pt; pt++)
            if (*pt == '\n' || *pt == '\r') *pt = ' ';
 
    return Buffer;
 
}
 
static
void EmitHeader(cmsIOHANDLER* m, const char* Title, cmsHPROFILE hProfile)
{
    time_t timer;
    cmsMLU *Description, *Copyright;
    char DescASCII[256], CopyrightASCII[256];
 
    time(&timer);
 
    Description = (cmsMLU*) cmsReadTag(hProfile, cmsSigProfileDescriptionTag);
    Copyright   = (cmsMLU*) cmsReadTag(hProfile, cmsSigCopyrightTag);
 
    DescASCII[0] = DescASCII[255] = 0;
    CopyrightASCII[0] = CopyrightASCII[255] = 0;
 
    if (Description != NULL) cmsMLUgetASCII(Description,  cmsNoLanguage, cmsNoCountry, DescASCII,       255);
    if (Copyright != NULL)   cmsMLUgetASCII(Copyright,    cmsNoLanguage, cmsNoCountry, CopyrightASCII,  255);
 
    _cmsIOPrintf(m, "%%!PS-Adobe-3.0\n");
    _cmsIOPrintf(m, "%%\n");
    _cmsIOPrintf(m, "%% %s\n", Title);
    _cmsIOPrintf(m, "%% Source: %s\n", RemoveCR(DescASCII));
    _cmsIOPrintf(m, "%%         %s\n", RemoveCR(CopyrightASCII));
    _cmsIOPrintf(m, "%% Created: %s", ctime(&timer)); // ctime appends a \n!!!
    _cmsIOPrintf(m, "%%\n");
    _cmsIOPrintf(m, "%%%%BeginResource\n");
 
}
 
 
// Emits White & Black point. White point is always D50, Black point is the device
// Black point adapted to D50.
 
static
void EmitWhiteBlackD50(cmsIOHANDLER* m, cmsCIEXYZ* BlackPoint)
{
 
    _cmsIOPrintf(m, "/BlackPoint [%f %f %f]\n", BlackPoint -> X,
                                          BlackPoint -> Y,
                                          BlackPoint -> Z);
 
    _cmsIOPrintf(m, "/WhitePoint [%f %f %f]\n", cmsD50_XYZ()->X,
                                          cmsD50_XYZ()->Y,
                                          cmsD50_XYZ()->Z);
}
 
 
static
void EmitRangeCheck(cmsIOHANDLER* m)
{
    _cmsIOPrintf(m, "dup 0.0 lt { pop 0.0 } if "
                    "dup 1.0 gt { pop 1.0 } if ");
 
}
 
// Does write the intent
 
static
void EmitIntent(cmsIOHANDLER* m, cmsUInt32Number RenderingIntent)
{
    const char *intent;
 
    switch (RenderingIntent) {
 
        case INTENT_PERCEPTUAL:            intent = "Perceptual"; break;
        case INTENT_RELATIVE_COLORIMETRIC: intent = "RelativeColorimetric"; break;
        case INTENT_ABSOLUTE_COLORIMETRIC: intent = "AbsoluteColorimetric"; break;
        case INTENT_SATURATION:            intent = "Saturation"; break;
 
        default: intent = "Undefined"; break;
    }
 
    _cmsIOPrintf(m, "/RenderingIntent (%s)\n", intent );
}
 
//
//  Convert L* to Y
//
//      Y = Yn*[ (L* + 16) / 116] ^ 3   if (L*) >= 6 / 29
//        = Yn*( L* / 116) / 7.787      if (L*) < 6 / 29
//
 
// Lab -> XYZ, see the discussion above
 
static
void EmitLab2XYZ(cmsIOHANDLER* m)
{
    _cmsIOPrintf(m, "/RangeABC [ 0 1 0 1 0 1]\n");
    _cmsIOPrintf(m, "/DecodeABC [\n");
    _cmsIOPrintf(m, "{100 mul  16 add 116 div } bind\n");
    _cmsIOPrintf(m, "{255 mul 128 sub 500 div } bind\n");
    _cmsIOPrintf(m, "{255 mul 128 sub 200 div } bind\n");
    _cmsIOPrintf(m, "]\n");
    _cmsIOPrintf(m, "/MatrixABC [ 1 1 1 1 0 0 0 0 -1]\n");
    _cmsIOPrintf(m, "/RangeLMN [ -0.236 1.254 0 1 -0.635 1.640 ]\n");
    _cmsIOPrintf(m, "/DecodeLMN [\n");
    _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");
    _cmsIOPrintf(m, "{dup 6 29 div ge {dup dup mul mul} {4 29 div sub 108 841 div mul} ifelse } bind\n");
    _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");
    _cmsIOPrintf(m, "]\n");
}
 
static
void EmitSafeGuardBegin(cmsIOHANDLER* m, const char* name)
{
    _cmsIOPrintf(m, "%%LCMS2: Save previous definition of %s on the operand stack\n", name);
    _cmsIOPrintf(m, "currentdict /%s known { /%s load } { null } ifelse\n", name, name);
}
 
static
void EmitSafeGuardEnd(cmsIOHANDLER* m, const char* name, int depth)
{
    _cmsIOPrintf(m, "%%LCMS2: Restore previous definition of %s\n", name);
    if (depth > 1) {
        // cycle topmost items on the stack to bring the previous definition to the front
        _cmsIOPrintf(m, "%d -1 roll ", depth);
    }
    _cmsIOPrintf(m, "dup null eq { pop currentdict /%s undef } { /%s exch def } ifelse\n", name, name);
}
 
// Outputs a table of words. It does use 16 bits
 
static
void Emit1Gamma(cmsIOHANDLER* m, cmsToneCurve* Table, const char* name)
{
    cmsUInt32Number i;
    cmsFloat64Number gamma;
 
    if (Table == NULL) return; // Error
 
    if (Table ->nEntries <= 0) return;  // Empty table
 
    // Suppress whole if identity
    if (cmsIsToneCurveLinear(Table)) return;
 
    // Check if is really an exponential. If so, emit "exp"
    gamma = cmsEstimateGamma(Table, 0.001);
     if (gamma > 0) {
            _cmsIOPrintf(m, "/%s { %g exp } bind def\n", name, gamma);
            return;
     }
 
    EmitSafeGuardBegin(m, "lcms2gammatable");
    _cmsIOPrintf(m, "/lcms2gammatable [");
 
    for (i=0; i < Table->nEntries; i++) {
	if (i % 10 == 0)
            _cmsIOPrintf(m, "\n  ");
        _cmsIOPrintf(m, "%d ", Table->Table16[i]);
    }
 
    _cmsIOPrintf(m, "] def\n");
 
 
    // Emit interpolation code
 
    // PostScript code                            Stack
    // ===============                            ========================
                                            	  // v
    _cmsIOPrintf(m, "/%s {\n  ", name);
 
    // Bounds check
    EmitRangeCheck(m);
 
    _cmsIOPrintf(m, "\n  //lcms2gammatable ");    // v tab
    _cmsIOPrintf(m, "dup ");                      // v tab tab
    _cmsIOPrintf(m, "length 1 sub ");             // v tab dom
    _cmsIOPrintf(m, "3 -1 roll ");                // tab dom v
    _cmsIOPrintf(m, "mul ");                      // tab val2
    _cmsIOPrintf(m, "dup ");                      // tab val2 val2
    _cmsIOPrintf(m, "dup ");                      // tab val2 val2 val2
    _cmsIOPrintf(m, "floor cvi ");                // tab val2 val2 cell0
    _cmsIOPrintf(m, "exch ");                     // tab val2 cell0 val2
    _cmsIOPrintf(m, "ceiling cvi ");              // tab val2 cell0 cell1
    _cmsIOPrintf(m, "3 index ");                  // tab val2 cell0 cell1 tab
    _cmsIOPrintf(m, "exch ");                     // tab val2 cell0 tab cell1
    _cmsIOPrintf(m, "get\n  ");                   // tab val2 cell0 y1
    _cmsIOPrintf(m, "4 -1 roll ");                // val2 cell0 y1 tab
    _cmsIOPrintf(m, "3 -1 roll ");                // val2 y1 tab cell0
    _cmsIOPrintf(m, "get ");                      // val2 y1 y0
    _cmsIOPrintf(m, "dup ");                      // val2 y1 y0 y0
    _cmsIOPrintf(m, "3 1 roll ");                 // val2 y0 y1 y0
    _cmsIOPrintf(m, "sub ");                      // val2 y0 (y1-y0)
    _cmsIOPrintf(m, "3 -1 roll ");                // y0 (y1-y0) val2
    _cmsIOPrintf(m, "dup ");                      // y0 (y1-y0) val2 val2
    _cmsIOPrintf(m, "floor cvi ");                // y0 (y1-y0) val2 floor(val2)
    _cmsIOPrintf(m, "sub ");                      // y0 (y1-y0) rest
    _cmsIOPrintf(m, "mul ");                      // y0 t1
    _cmsIOPrintf(m, "add ");                      // y
    _cmsIOPrintf(m, "65535 div\n");               // result
 
    _cmsIOPrintf(m, "} bind def\n");
 
    EmitSafeGuardEnd(m, "lcms2gammatable", 1);
}
 
 
// Compare gamma table
 
static
cmsBool GammaTableEquals(cmsUInt16Number* g1, cmsUInt16Number* g2, cmsUInt32Number nG1, cmsUInt32Number nG2)
{
    if (nG1 != nG2) return FALSE;
    return memcmp(g1, g2, nG1 * sizeof(cmsUInt16Number)) == 0;
}
 
 
// Does write a set of gamma curves
 
static
void EmitNGamma(cmsIOHANDLER* m, cmsUInt32Number n, cmsToneCurve* g[], const char* nameprefix)
{
    cmsUInt32Number i;
    static char buffer[2048];
 
    for( i=0; i < n; i++ )
    {
        if (g[i] == NULL) return; // Error
 
        if (i > 0 && GammaTableEquals(g[i-1]->Table16, g[i]->Table16, g[i-1]->nEntries, g[i]->nEntries)) {
 
            _cmsIOPrintf(m, "/%s%d /%s%d load def\n", nameprefix, i, nameprefix, i-1);
        }
        else {
            snprintf(buffer, sizeof(buffer), "%s%d", nameprefix, (int) i);
	    buffer[sizeof(buffer)-1] = '\0';
            Emit1Gamma(m, g[i], buffer);
        }
    }
 
}
 
 
// Following code dumps a LUT onto memory stream
 
 
// This is the sampler. Intended to work in SAMPLER_INSPECT mode,
// that is, the callback will be called for each knot with
//
//          In[]  The grid location coordinates, normalized to 0..ffff
//          Out[] The Pipeline values, normalized to 0..ffff
//
//  Returning a value other than 0 does terminate the sampling process
//
//  Each row contains Pipeline values for all but first component. So, I
//  detect row changing by keeping a copy of last value of first
//  component. -1 is used to mark beginning of whole block.
 
static
int OutputValueSampler(CMSREGISTER const cmsUInt16Number In[], CMSREGISTER cmsUInt16Number Out[], CMSREGISTER void* Cargo)
{
    cmsPsSamplerCargo* sc = (cmsPsSamplerCargo*) Cargo;
    cmsUInt32Number i;
 
 
    if (sc -> FixWhite) {
 
        if (In[0] == 0xFFFF) {  // Only in L* = 100, ab = [-8..8]
 
            if ((In[1] >= 0x7800 && In[1] <= 0x8800) &&
                (In[2] >= 0x7800 && In[2] <= 0x8800)) {
 
                cmsUInt16Number* Black;
                cmsUInt16Number* White;
                cmsUInt32Number nOutputs;
 
                if (!_cmsEndPointsBySpace(sc ->ColorSpace, &White, &Black, &nOutputs))
                        return 0;
 
                for (i=0; i < nOutputs; i++)
                        Out[i] = White[i];
            }
 
 
        }
    }
 
 
    // Hadle the parenthesis on rows
 
    if (In[0] != sc ->FirstComponent) {
 
            if (sc ->FirstComponent != -1) {
 
                    _cmsIOPrintf(sc ->m, sc ->PostMin);
                    sc ->SecondComponent = -1;
                    _cmsIOPrintf(sc ->m, sc ->PostMaj);
            }
 
            // Begin block
            _cmsPSActualColumn = 0;
 
            _cmsIOPrintf(sc ->m, sc ->PreMaj);
            sc ->FirstComponent = In[0];
    }
 
 
      if (In[1] != sc ->SecondComponent) {
 
            if (sc ->SecondComponent != -1) {
 
                    _cmsIOPrintf(sc ->m, sc ->PostMin);
            }
 
            _cmsIOPrintf(sc ->m, sc ->PreMin);
            sc ->SecondComponent = In[1];
    }
 
      // Dump table.
 
      for (i=0; i < sc -> Pipeline ->Params->nOutputs; i++) {
 
          cmsUInt16Number wWordOut = Out[i];
          cmsUInt8Number wByteOut;           // Value as byte
 
 
          // We always deal with Lab4
 
          wByteOut = Word2Byte(wWordOut);
          WriteByte(sc -> m, wByteOut);
      }
 
      return 1;
}
 
// Writes a Pipeline on memstream. Could be 8 or 16 bits based
 
static
void WriteCLUT(cmsIOHANDLER* m, cmsStage* mpe, const char* PreMaj,
                                               const char* PostMaj,
                                               const char* PreMin,
                                               const char* PostMin,
                                               int FixWhite,
                                               cmsColorSpaceSignature ColorSpace)
{
    cmsUInt32Number i;
    cmsPsSamplerCargo sc;
 
    sc.FirstComponent = -1;
    sc.SecondComponent = -1;
    sc.Pipeline = (_cmsStageCLutData *) mpe ->Data;
    sc.m   = m;
    sc.PreMaj = PreMaj;
    sc.PostMaj= PostMaj;
 
    sc.PreMin   = PreMin;
    sc.PostMin  = PostMin;
    sc.FixWhite = FixWhite;
    sc.ColorSpace = ColorSpace;
 
    _cmsIOPrintf(m, "[");
 
    for (i=0; i < sc.Pipeline->Params->nInputs; i++)
        _cmsIOPrintf(m, " %d ", sc.Pipeline->Params->nSamples[i]);
 
    _cmsIOPrintf(m, " [\n");
 
    cmsStageSampleCLut16bit(mpe, OutputValueSampler, (void*) &sc, SAMPLER_INSPECT);
 
    _cmsIOPrintf(m, PostMin);
    _cmsIOPrintf(m, PostMaj);
    _cmsIOPrintf(m, "] ");
 
}
 
 
// Dumps CIEBasedA Color Space Array
 
static
int EmitCIEBasedA(cmsIOHANDLER* m, cmsToneCurve* Curve, cmsCIEXYZ* BlackPoint)
{
 
    _cmsIOPrintf(m, "[ /CIEBasedA\n");
    _cmsIOPrintf(m, "  <<\n");
 
    EmitSafeGuardBegin(m, "lcms2gammaproc");
    Emit1Gamma(m, Curve, "lcms2gammaproc");
 
    _cmsIOPrintf(m, "/DecodeA /lcms2gammaproc load\n");
    EmitSafeGuardEnd(m, "lcms2gammaproc", 3);
 
    _cmsIOPrintf(m, "/MatrixA [ 0.9642 1.0000 0.8249 ]\n");
    _cmsIOPrintf(m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
 
    EmitWhiteBlackD50(m, BlackPoint);
    EmitIntent(m, INTENT_PERCEPTUAL);
 
    _cmsIOPrintf(m, ">>\n");
    _cmsIOPrintf(m, "]\n");
 
    return 1;
}
 
 
// Dumps CIEBasedABC Color Space Array
 
static
int EmitCIEBasedABC(cmsIOHANDLER* m, cmsFloat64Number* Matrix, cmsToneCurve** CurveSet, cmsCIEXYZ* BlackPoint)
{
    int i;
 
    _cmsIOPrintf(m, "[ /CIEBasedABC\n");
    _cmsIOPrintf(m, "<<\n");
 
    EmitSafeGuardBegin(m, "lcms2gammaproc0");
    EmitSafeGuardBegin(m, "lcms2gammaproc1");
    EmitSafeGuardBegin(m, "lcms2gammaproc2");
    EmitNGamma(m, 3, CurveSet, "lcms2gammaproc");
    _cmsIOPrintf(m, "/DecodeABC [\n");
    _cmsIOPrintf(m, "   /lcms2gammaproc0 load\n");
    _cmsIOPrintf(m, "   /lcms2gammaproc1 load\n");
    _cmsIOPrintf(m, "   /lcms2gammaproc2 load\n");
    _cmsIOPrintf(m, "]\n");
    EmitSafeGuardEnd(m, "lcms2gammaproc2", 3);
    EmitSafeGuardEnd(m, "lcms2gammaproc1", 3);
    EmitSafeGuardEnd(m, "lcms2gammaproc0", 3);
 
    _cmsIOPrintf(m, "/MatrixABC [ " );
 
    for( i=0; i < 3; i++ ) {
 
        _cmsIOPrintf(m, "%.6f %.6f %.6f ", Matrix[i + 3*0],
                                           Matrix[i + 3*1],
                                           Matrix[i + 3*2]);
    }
 
 
    _cmsIOPrintf(m, "]\n");
 
    _cmsIOPrintf(m, "/RangeLMN [ 0.0 0.9642 0.0 1.0000 0.0 0.8249 ]\n");
 
    EmitWhiteBlackD50(m, BlackPoint);
    EmitIntent(m, INTENT_PERCEPTUAL);
 
    _cmsIOPrintf(m, ">>\n");
    _cmsIOPrintf(m, "]\n");
 
 
    return 1;
}
 
 
static
int EmitCIEBasedDEF(cmsIOHANDLER* m, cmsPipeline* Pipeline, cmsUInt32Number Intent, cmsCIEXYZ* BlackPoint)
{
    const char* PreMaj;
    const char* PostMaj;
    const char* PreMin, * PostMin;
    cmsStage* mpe;
    int i, numchans;
    static char buffer[2048];
 
    mpe = Pipeline->Elements;
 
    switch (cmsStageInputChannels(mpe)) {
    case 3:
        _cmsIOPrintf(m, "[ /CIEBasedDEF\n");
        PreMaj = "<";
        PostMaj = ">\n";
        PreMin = PostMin = "";
        break;
 
    case 4:
        _cmsIOPrintf(m, "[ /CIEBasedDEFG\n");
        PreMaj = "[";
        PostMaj = "]\n";
        PreMin = "<";
        PostMin = ">\n";
        break;
 
    default:
        return 0;
 
    }
 
    _cmsIOPrintf(m, "<<\n");
 
    if (cmsStageType(mpe) == cmsSigCurveSetElemType) {
 
        numchans = (int) cmsStageOutputChannels(mpe);
        for (i = 0; i < numchans; ++i) {
            snprintf(buffer, sizeof(buffer), "lcms2gammaproc%d", i);
            buffer[sizeof(buffer) - 1] = '\0';
            EmitSafeGuardBegin(m, buffer);
        }
        EmitNGamma(m, cmsStageOutputChannels(mpe), _cmsStageGetPtrToCurveSet(mpe), "lcms2gammaproc");
        _cmsIOPrintf(m, "/DecodeDEF [\n");
        for (i = 0; i < numchans; ++i) {
            snprintf(buffer, sizeof(buffer), "  /lcms2gammaproc%d load\n", i);
            buffer[sizeof(buffer) - 1] = '\0';
            _cmsIOPrintf(m, buffer);
        }
        _cmsIOPrintf(m, "]\n");
        for (i = numchans - 1; i >= 0; --i) {
            snprintf(buffer, sizeof(buffer), "lcms2gammaproc%d", i);
            buffer[sizeof(buffer) - 1] = '\0';
            EmitSafeGuardEnd(m, buffer, 3);
        }
 
        mpe = mpe->Next;
    }
 
    if (cmsStageType(mpe) == cmsSigCLutElemType) {
 
        _cmsIOPrintf(m, "/Table ");
        WriteCLUT(m, mpe, PreMaj, PostMaj, PreMin, PostMin, FALSE, (cmsColorSpaceSignature)0);
        _cmsIOPrintf(m, "]\n");
    }
 
    EmitLab2XYZ(m);
    EmitWhiteBlackD50(m, BlackPoint);
    EmitIntent(m, Intent);
 
    _cmsIOPrintf(m, "   >>\n");
    _cmsIOPrintf(m, "]\n");
 
    return 1;
}
 
// Generates a curve from a gray profile
 
static
cmsToneCurve* ExtractGray2Y(cmsContext ContextID, cmsHPROFILE hProfile, cmsUInt32Number Intent)
{
    cmsToneCurve* Out = cmsBuildTabulatedToneCurve16(ContextID, 256, NULL);
    cmsHPROFILE hXYZ  = cmsCreateXYZProfile();
    cmsHTRANSFORM xform = cmsCreateTransformTHR(ContextID, hProfile, TYPE_GRAY_8, hXYZ, TYPE_XYZ_DBL, Intent, cmsFLAGS_NOOPTIMIZE);
    int i;
 
    if (Out != NULL && xform != NULL) {
        for (i=0; i < 256; i++) {
 
            cmsUInt8Number Gray = (cmsUInt8Number) i;
            cmsCIEXYZ XYZ;
 
            cmsDoTransform(xform, &Gray, &XYZ, 1);
 
            Out ->Table16[i] =_cmsQuickSaturateWord(XYZ.Y * 65535.0);
        }
    }
 
    if (xform) cmsDeleteTransform(xform);
    if (hXYZ) cmsCloseProfile(hXYZ);
    return Out;
}
 
 
 
// Because PostScript has only 8 bits in /Table, we should use
// a more perceptually uniform space... I do choose Lab.
 
static
int WriteInputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
{
    cmsHPROFILE hLab;
    cmsHTRANSFORM xform;
    cmsUInt32Number nChannels;
    cmsUInt32Number InputFormat;
    int rc;
    cmsHPROFILE Profiles[2];
    cmsCIEXYZ BlackPointAdaptedToD50;
 
    // Does create a device-link based transform.
    // The DeviceLink is next dumped as working CSA.
 
    InputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE);
    nChannels   = T_CHANNELS(InputFormat);
 
 
    cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0);
 
    // Adjust output to Lab4
    hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
 
    Profiles[0] = hProfile;
    Profiles[1] = hLab;
 
    xform = cmsCreateMultiprofileTransform(Profiles, 2,  InputFormat, TYPE_Lab_DBL, Intent, 0);
    cmsCloseProfile(hLab);
 
    if (xform == NULL) {
 
        cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Profile -> Lab");
        return 0;
    }
 
    // Only 1, 3 and 4 channels are allowed
 
    switch (nChannels) {
 
    case 1: {
            cmsToneCurve* Gray2Y = ExtractGray2Y(m ->ContextID, hProfile, Intent);
            EmitCIEBasedA(m, Gray2Y, &BlackPointAdaptedToD50);
            cmsFreeToneCurve(Gray2Y);
            }
            break;
 
    case 3:
    case 4: {
            cmsUInt32Number OutFrm = TYPE_Lab_16;
            cmsPipeline* DeviceLink;
            _cmsTRANSFORM* v = (_cmsTRANSFORM*) xform;
 
            DeviceLink = cmsPipelineDup(v ->Lut);
            if (DeviceLink == NULL) return 0;
 
            dwFlags |= cmsFLAGS_FORCE_CLUT;
            _cmsOptimizePipeline(m->ContextID, &DeviceLink, Intent, &InputFormat, &OutFrm, &dwFlags);
 
            rc = EmitCIEBasedDEF(m, DeviceLink, Intent, &BlackPointAdaptedToD50);
            cmsPipelineFree(DeviceLink);
            if (rc == 0) return 0;
            }
            break;
 
    default:
 
        cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Only 3, 4 channels are supported for CSA. This profile has %d channels.", nChannels);
        return 0;
    }
 
 
    cmsDeleteTransform(xform);
 
    return 1;
}
 
static
cmsFloat64Number* GetPtrToMatrix(const cmsStage* mpe)
{
    _cmsStageMatrixData* Data = (_cmsStageMatrixData*) mpe ->Data;
 
    return Data -> Double;
}
 
 
// Does create CSA based on matrix-shaper. Allowed types are gray and RGB based
static
int WriteInputMatrixShaper(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsStage* Matrix, cmsStage* Shaper)
{
    cmsColorSpaceSignature ColorSpace;
    int rc;
    cmsCIEXYZ BlackPointAdaptedToD50;
 
    ColorSpace = cmsGetColorSpace(hProfile);
 
    cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, INTENT_RELATIVE_COLORIMETRIC, 0);
 
    if (ColorSpace == cmsSigGrayData) {
 
        cmsToneCurve** ShaperCurve = _cmsStageGetPtrToCurveSet(Shaper);
        rc = EmitCIEBasedA(m, ShaperCurve[0], &BlackPointAdaptedToD50);
 
    }
    else
        if (ColorSpace == cmsSigRgbData) {
 
            cmsMAT3 Mat;
            int i, j;
 
            memmove(&Mat, GetPtrToMatrix(Matrix), sizeof(Mat));
 
            for (i = 0; i < 3; i++)
                for (j = 0; j < 3; j++)
                    Mat.v[i].n[j] *= MAX_ENCODEABLE_XYZ;
 
            rc = EmitCIEBasedABC(m, (cmsFloat64Number *)&Mat,
                _cmsStageGetPtrToCurveSet(Shaper),
                &BlackPointAdaptedToD50);
        }
        else {
 
            cmsSignalError(m->ContextID, cmsERROR_COLORSPACE_CHECK, "Profile is not suitable for CSA. Unsupported colorspace.");
            return 0;
        }
 
    return rc;
}
 
 
 
// Creates a PostScript color list from a named profile data.
// This is a HP extension, and it works in Lab instead of XYZ
 
static
int WriteNamedColorCSA(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent)
{
    cmsHTRANSFORM xform;
    cmsHPROFILE   hLab;
    cmsUInt32Number i, nColors;
    char ColorName[cmsMAX_PATH];
    cmsNAMEDCOLORLIST* NamedColorList;
 
    hLab  = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
    xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, hLab, TYPE_Lab_DBL, Intent, 0);
    if (xform == NULL) return 0;
 
    NamedColorList = cmsGetNamedColorList(xform);
    if (NamedColorList == NULL) return 0;
 
    _cmsIOPrintf(m, "<<\n");
    _cmsIOPrintf(m, "(colorlistcomment) (%s)\n", "Named color CSA");
    _cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
    _cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");
 
    nColors   = cmsNamedColorCount(NamedColorList);
 
 
    for (i=0; i < nColors; i++) {
 
        cmsUInt16Number In[1];
        cmsCIELab Lab;
 
        In[0] = (cmsUInt16Number) i;
 
        if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL))
                continue;
 
        cmsDoTransform(xform, In, &Lab, 1);
        _cmsIOPrintf(m, "  (%s) [ %.3f %.3f %.3f ]\n", ColorName, Lab.L, Lab.a, Lab.b);
    }
 
 
 
    _cmsIOPrintf(m, ">>\n");
 
    cmsDeleteTransform(xform);
    cmsCloseProfile(hLab);
    return 1;
}
 
 
// Does create a Color Space Array on XYZ colorspace for PostScript usage
static
cmsUInt32Number GenerateCSA(cmsContext ContextID,
                            cmsHPROFILE hProfile,
                            cmsUInt32Number Intent,
                            cmsUInt32Number dwFlags,
                            cmsIOHANDLER* mem)
{
    cmsUInt32Number dwBytesUsed;
    cmsPipeline* lut = NULL;
    cmsStage* Matrix, *Shaper;
 
 
    // Is a named color profile?
    if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
 
        if (!WriteNamedColorCSA(mem, hProfile, Intent)) goto Error;
    }
    else {
 
 
        // Any profile class are allowed (including devicelink), but
        // output (PCS) colorspace must be XYZ or Lab
        cmsColorSpaceSignature ColorSpace = cmsGetPCS(hProfile);
 
        if (ColorSpace != cmsSigXYZData &&
            ColorSpace != cmsSigLabData) {
 
                cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "Invalid output color space");
                goto Error;
        }
 
 
        // Read the lut with all necessary conversion stages
        lut = _cmsReadInputLUT(hProfile, Intent);
        if (lut == NULL) goto Error;
 
 
        // Tone curves + matrix can be implemented without any LUT
        if (cmsPipelineCheckAndRetreiveStages(lut, 2, cmsSigCurveSetElemType, cmsSigMatrixElemType, &Shaper, &Matrix)) {
 
            if (!WriteInputMatrixShaper(mem, hProfile, Matrix, Shaper)) goto Error;
 
        }
        else {
           // We need a LUT for the rest
           if (!WriteInputLUT(mem, hProfile, Intent, dwFlags)) goto Error;
        }
    }
 
 
    // Done, keep memory usage
    dwBytesUsed = mem ->UsedSpace;
 
    // Get rid of LUT
    if (lut != NULL) cmsPipelineFree(lut);
 
    // Finally, return used byte count
    return dwBytesUsed;
 
Error:
    if (lut != NULL) cmsPipelineFree(lut);
    return 0;
}
 
// ------------------------------------------------------ Color Rendering Dictionary (CRD)
 
 
 
/*
 
  Black point compensation plus chromatic adaptation:
 
  Step 1 - Chromatic adaptation
  =============================
 
          WPout
    X = ------- PQR
          Wpin
 
  Step 2 - Black point compensation
  =================================
 
          (WPout - BPout)*X - WPout*(BPin - BPout)
    out = ---------------------------------------
                        WPout - BPin
 
 
  Algorithm discussion
  ====================
 
  TransformPQR(WPin, BPin, WPout, BPout, PQR)
 
  Wpin,etc= { Xws Yws Zws Pws Qws Rws }
 
 
  Algorithm             Stack 0...n
  ===========================================================
                        PQR BPout WPout BPin WPin
  4 index 3 get         WPin PQR BPout WPout BPin WPin
  div                   (PQR/WPin) BPout WPout BPin WPin
  2 index 3 get         WPout (PQR/WPin) BPout WPout BPin WPin
  mult                  WPout*(PQR/WPin) BPout WPout BPin WPin
 
  2 index 3 get         WPout WPout*(PQR/WPin) BPout WPout BPin WPin
  2 index 3 get         BPout WPout WPout*(PQR/WPin) BPout WPout BPin WPin
  sub                   (WPout-BPout) WPout*(PQR/WPin) BPout WPout BPin WPin
  mult                  (WPout-BPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
 
  2 index 3 get         WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
  4 index 3 get         BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
  3 index 3 get         BPout BPin WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
 
  sub                   (BPin-BPout) WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
  mult                  (BPin-BPout)*WPout (BPout-WPout)* WPout*(PQR/WPin) BPout WPout BPin WPin
  sub                   (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
 
  3 index 3 get         BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
  3 index 3 get         WPout BPin (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
  exch
  sub                   (WPout-BPin) (BPout-WPout)* WPout*(PQR/WPin)-(BPin-BPout)*WPout BPout WPout BPin WPin
  div
 
  exch pop
  exch pop
  exch pop
  exch pop
 
*/
 
 
static
void EmitPQRStage(cmsIOHANDLER* m, cmsHPROFILE hProfile, int DoBPC, int lIsAbsolute)
{
 
 
        if (lIsAbsolute) {
 
            // For absolute colorimetric intent, encode back to relative
            // and generate a relative Pipeline
 
            // Relative encoding is obtained across XYZpcs*(D50/WhitePoint)
 
            cmsCIEXYZ White;
 
            _cmsReadMediaWhitePoint(&White, hProfile);
 
            _cmsIOPrintf(m,"/MatrixPQR [1 0 0 0 1 0 0 0 1 ]\n");
            _cmsIOPrintf(m,"/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");
 
            _cmsIOPrintf(m, "%% Absolute colorimetric -- encode to relative to maximize LUT usage\n"
                      "/TransformPQR [\n"
                      "{0.9642 mul %g div exch pop exch pop exch pop exch pop} bind\n"
                      "{1.0000 mul %g div exch pop exch pop exch pop exch pop} bind\n"
                      "{0.8249 mul %g div exch pop exch pop exch pop exch pop} bind\n]\n",
                      White.X, White.Y, White.Z);
            return;
        }
 
 
        _cmsIOPrintf(m,"%% Bradford Cone Space\n"
                 "/MatrixPQR [0.8951 -0.7502 0.0389 0.2664 1.7135 -0.0685 -0.1614 0.0367 1.0296 ] \n");
 
        _cmsIOPrintf(m, "/RangePQR [ -0.5 2 -0.5 2 -0.5 2 ]\n");
 
 
        // No BPC
 
        if (!DoBPC) {
 
            _cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space\n"
                      "/TransformPQR [\n"
                      "{exch pop exch 3 get mul exch pop exch 3 get div} bind\n"
                      "{exch pop exch 4 get mul exch pop exch 4 get div} bind\n"
                      "{exch pop exch 5 get mul exch pop exch 5 get div} bind\n]\n");
        } else {
 
            // BPC
 
            _cmsIOPrintf(m, "%% VonKries-like transform in Bradford Cone Space plus BPC\n"
                      "/TransformPQR [\n");
 
            _cmsIOPrintf(m, "{4 index 3 get div 2 index 3 get mul "
                    "2 index 3 get 2 index 3 get sub mul "
                    "2 index 3 get 4 index 3 get 3 index 3 get sub mul sub "
                    "3 index 3 get 3 index 3 get exch sub div "
                    "exch pop exch pop exch pop exch pop } bind\n");
 
            _cmsIOPrintf(m, "{4 index 4 get div 2 index 4 get mul "
                    "2 index 4 get 2 index 4 get sub mul "
                    "2 index 4 get 4 index 4 get 3 index 4 get sub mul sub "
                    "3 index 4 get 3 index 4 get exch sub div "
                    "exch pop exch pop exch pop exch pop } bind\n");
 
            _cmsIOPrintf(m, "{4 index 5 get div 2 index 5 get mul "
                    "2 index 5 get 2 index 5 get sub mul "
                    "2 index 5 get 4 index 5 get 3 index 5 get sub mul sub "
                    "3 index 5 get 3 index 5 get exch sub div "
                    "exch pop exch pop exch pop exch pop } bind\n]\n");
 
        }
}
 
 
static
void EmitXYZ2Lab(cmsIOHANDLER* m)
{
    _cmsIOPrintf(m, "/RangeLMN [ -0.635 2.0 0 2 -0.635 2.0 ]\n");
    _cmsIOPrintf(m, "/EncodeLMN [\n");
    _cmsIOPrintf(m, "{ 0.964200  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
    _cmsIOPrintf(m, "{ 1.000000  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
    _cmsIOPrintf(m, "{ 0.824900  div dup 0.008856 le {7.787 mul 16 116 div add}{1 3 div exp} ifelse } bind\n");
    _cmsIOPrintf(m, "]\n");
    _cmsIOPrintf(m, "/MatrixABC [ 0 1 0 1 -1 1 0 0 -1 ]\n");
    _cmsIOPrintf(m, "/EncodeABC [\n");
 
 
    _cmsIOPrintf(m, "{ 116 mul  16 sub 100 div  } bind\n");
    _cmsIOPrintf(m, "{ 500 mul 128 add 256 div  } bind\n");
    _cmsIOPrintf(m, "{ 200 mul 128 add 256 div  } bind\n");
 
 
    _cmsIOPrintf(m, "]\n");
 
 
}
 
// Due to impedance mismatch between XYZ and almost all RGB and CMYK spaces
// I choose to dump LUTS in Lab instead of XYZ. There is still a lot of wasted
// space on 3D CLUT, but since space seems not to be a problem here, 33 points
// would give a reasonable accuracy. Note also that CRD tables must operate in
// 8 bits.
 
static
int WriteOutputLUT(cmsIOHANDLER* m, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
{
    cmsHPROFILE hLab;
    cmsHTRANSFORM xform;
    cmsUInt32Number i, nChannels;
    cmsUInt32Number OutputFormat;
    _cmsTRANSFORM* v;
    cmsPipeline* DeviceLink;
    cmsHPROFILE Profiles[3];
    cmsCIEXYZ BlackPointAdaptedToD50;
    cmsBool lDoBPC = (cmsBool) (dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION);
    cmsBool lFixWhite = (cmsBool) !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP);
    cmsUInt32Number InFrm = TYPE_Lab_16;
    cmsUInt32Number RelativeEncodingIntent;
    cmsColorSpaceSignature ColorSpace;
 
 
    hLab = cmsCreateLab4ProfileTHR(m ->ContextID, NULL);
    if (hLab == NULL) return 0;
 
    OutputFormat = cmsFormatterForColorspaceOfProfile(hProfile, 2, FALSE);
    nChannels    = T_CHANNELS(OutputFormat);
 
    ColorSpace = cmsGetColorSpace(hProfile);
 
    // For absolute colorimetric, the LUT is encoded as relative in order to preserve precision.
 
    RelativeEncodingIntent = Intent;
    if (RelativeEncodingIntent == INTENT_ABSOLUTE_COLORIMETRIC)
        RelativeEncodingIntent = INTENT_RELATIVE_COLORIMETRIC;
 
 
    // Use V4 Lab always
    Profiles[0] = hLab;
    Profiles[1] = hProfile;
 
    xform = cmsCreateMultiprofileTransformTHR(m ->ContextID,
                                              Profiles, 2, TYPE_Lab_DBL,
                                              OutputFormat, RelativeEncodingIntent, 0);
    cmsCloseProfile(hLab);
 
    if (xform == NULL) {
 
        cmsSignalError(m ->ContextID, cmsERROR_COLORSPACE_CHECK, "Cannot create transform Lab -> Profile in CRD creation");
        return 0;
    }
 
    // Get a copy of the internal devicelink
    v = (_cmsTRANSFORM*) xform;
    DeviceLink = cmsPipelineDup(v ->Lut);
    if (DeviceLink == NULL) return 0;
 
 
    // We need a CLUT
    dwFlags |= cmsFLAGS_FORCE_CLUT;
    _cmsOptimizePipeline(m->ContextID, &DeviceLink, RelativeEncodingIntent, &InFrm, &OutputFormat, &dwFlags);
 
    _cmsIOPrintf(m, "<<\n");
    _cmsIOPrintf(m, "/ColorRenderingType 1\n");
 
 
    cmsDetectBlackPoint(&BlackPointAdaptedToD50, hProfile, Intent, 0);
 
    // Emit headers, etc.
    EmitWhiteBlackD50(m, &BlackPointAdaptedToD50);
    EmitPQRStage(m, hProfile, lDoBPC, Intent == INTENT_ABSOLUTE_COLORIMETRIC);
    EmitXYZ2Lab(m);
 
 
    // FIXUP: map Lab (100, 0, 0) to perfect white, because the particular encoding for Lab
    // does map a=b=0 not falling into any specific node. Since range a,b goes -128..127,
    // zero is slightly moved towards right, so assure next node (in L=100 slice) is mapped to
    // zero. This would sacrifice a bit of highlights, but failure to do so would cause
    // scum dot. Ouch.
 
    if (Intent == INTENT_ABSOLUTE_COLORIMETRIC)
            lFixWhite = FALSE;
 
    _cmsIOPrintf(m, "/RenderTable ");
 
 
    WriteCLUT(m, cmsPipelineGetPtrToFirstStage(DeviceLink), "<", ">\n", "", "", lFixWhite, ColorSpace);
 
    _cmsIOPrintf(m, " %d {} bind ", nChannels);
 
    for (i=1; i < nChannels; i++)
            _cmsIOPrintf(m, "dup ");
 
    _cmsIOPrintf(m, "]\n");
 
 
    EmitIntent(m, Intent);
 
    _cmsIOPrintf(m, ">>\n");
 
    if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
 
        _cmsIOPrintf(m, "/Current exch /ColorRendering defineresource pop\n");
    }
 
    cmsPipelineFree(DeviceLink);
    cmsDeleteTransform(xform);
 
    return 1;
}
 
 
// Builds a ASCII string containing colorant list in 0..1.0 range
static
void BuildColorantList(char *Colorant, cmsUInt32Number nColorant, cmsUInt16Number Out[])
{
    char Buff[32];
    cmsUInt32Number j;
 
    Colorant[0] = 0;
    if (nColorant > cmsMAXCHANNELS)
        nColorant = cmsMAXCHANNELS;
 
    for (j = 0; j < nColorant; j++) {
 
        snprintf(Buff, 31, "%.3f", Out[j] / 65535.0);
        Buff[31] = 0;
        strcat(Colorant, Buff);
        if (j < nColorant - 1)
            strcat(Colorant, " ");
 
    }
}
 
 
// Creates a PostScript color list from a named profile data.
// This is a HP extension.
 
static
int WriteNamedColorCRD(cmsIOHANDLER* m, cmsHPROFILE hNamedColor, cmsUInt32Number Intent, cmsUInt32Number dwFlags)
{
    cmsHTRANSFORM xform;
    cmsUInt32Number i, nColors, nColorant;
    cmsUInt32Number OutputFormat;
    char ColorName[cmsMAX_PATH];
    char Colorant[512];
    cmsNAMEDCOLORLIST* NamedColorList;
 
 
    OutputFormat = cmsFormatterForColorspaceOfProfile(hNamedColor, 2, FALSE);
    nColorant    = T_CHANNELS(OutputFormat);
 
 
    xform = cmsCreateTransform(hNamedColor, TYPE_NAMED_COLOR_INDEX, NULL, OutputFormat, Intent, dwFlags);
    if (xform == NULL) return 0;
 
 
    NamedColorList = cmsGetNamedColorList(xform);
    if (NamedColorList == NULL) return 0;
 
    _cmsIOPrintf(m, "<<\n");
    _cmsIOPrintf(m, "(colorlistcomment) (%s) \n", "Named profile");
    _cmsIOPrintf(m, "(Prefix) [ (Pantone ) (PANTONE ) ]\n");
    _cmsIOPrintf(m, "(Suffix) [ ( CV) ( CVC) ( C) ]\n");
 
    nColors   = cmsNamedColorCount(NamedColorList);
 
    for (i=0; i < nColors; i++) {
 
        cmsUInt16Number In[1];
        cmsUInt16Number Out[cmsMAXCHANNELS];
 
        In[0] = (cmsUInt16Number) i;
 
        if (!cmsNamedColorInfo(NamedColorList, i, ColorName, NULL, NULL, NULL, NULL))
                continue;
 
        cmsDoTransform(xform, In, Out, 1);
        BuildColorantList(Colorant, nColorant, Out);
        _cmsIOPrintf(m, "  (%s) [ %s ]\n", ColorName, Colorant);
    }
 
    _cmsIOPrintf(m, "   >>");
 
    if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
 
    _cmsIOPrintf(m, " /Current exch /HPSpotTable defineresource pop\n");
    }
 
    cmsDeleteTransform(xform);
    return 1;
}
 
 
 
// This one does create a Color Rendering Dictionary.
// CRD are always LUT-Based, no matter if profile is
// implemented as matrix-shaper.
 
static
cmsUInt32Number  GenerateCRD(cmsContext ContextID,
                             cmsHPROFILE hProfile,
                             cmsUInt32Number Intent, cmsUInt32Number dwFlags,
                             cmsIOHANDLER* mem)
{
    cmsUInt32Number dwBytesUsed;
 
    if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
 
        EmitHeader(mem, "Color Rendering Dictionary (CRD)", hProfile);
    }
 
 
    // Is a named color profile?
    if (cmsGetDeviceClass(hProfile) == cmsSigNamedColorClass) {
 
        if (!WriteNamedColorCRD(mem, hProfile, Intent, dwFlags)) {
            return 0;
        }
    }
    else {
 
        // CRD are always implemented as LUT
 
        if (!WriteOutputLUT(mem, hProfile, Intent, dwFlags)) {
            return 0;
        }
    }
 
    if (!(dwFlags & cmsFLAGS_NODEFAULTRESOURCEDEF)) {
 
        _cmsIOPrintf(mem, "%%%%EndResource\n");
        _cmsIOPrintf(mem, "\n%% CRD End\n");
    }
 
    // Done, keep memory usage
    dwBytesUsed = mem ->UsedSpace;
 
    // Finally, return used byte count
    return dwBytesUsed;
 
    cmsUNUSED_PARAMETER(ContextID);
}
 
 
 
 
cmsUInt32Number CMSEXPORT cmsGetPostScriptColorResource(cmsContext ContextID,
                                                               cmsPSResourceType Type,
                                                               cmsHPROFILE hProfile,
                                                               cmsUInt32Number Intent,
                                                               cmsUInt32Number dwFlags,
                                                               cmsIOHANDLER* io)
{
    cmsUInt32Number  rc;
 
 
    switch (Type) {
 
        case cmsPS_RESOURCE_CSA:
            rc = GenerateCSA(ContextID, hProfile, Intent, dwFlags, io);
            break;
 
        default:
        case cmsPS_RESOURCE_CRD:
            rc = GenerateCRD(ContextID, hProfile, Intent, dwFlags, io);
            break;
    }
 
    return rc;
}
 
 
 
cmsUInt32Number CMSEXPORT cmsGetPostScriptCRD(cmsContext ContextID,
                              cmsHPROFILE hProfile,
                              cmsUInt32Number Intent, cmsUInt32Number dwFlags,
                              void* Buffer, cmsUInt32Number dwBufferLen)
{
    cmsIOHANDLER* mem;
    cmsUInt32Number dwBytesUsed;
 
    // Set up the serialization engine
    if (Buffer == NULL)
        mem = cmsOpenIOhandlerFromNULL(ContextID);
    else
        mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w");
 
    if (!mem) return 0;
 
    dwBytesUsed =  cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CRD, hProfile, Intent, dwFlags, mem);
 
    // Get rid of memory stream
    cmsCloseIOhandler(mem);
 
    return dwBytesUsed;
}
 
 
 
// Does create a Color Space Array on XYZ colorspace for PostScript usage
cmsUInt32Number CMSEXPORT cmsGetPostScriptCSA(cmsContext ContextID,
                                              cmsHPROFILE hProfile,
                                              cmsUInt32Number Intent,
                                              cmsUInt32Number dwFlags,
                                              void* Buffer,
                                              cmsUInt32Number dwBufferLen)
{
    cmsIOHANDLER* mem;
    cmsUInt32Number dwBytesUsed;
 
    if (Buffer == NULL)
        mem = cmsOpenIOhandlerFromNULL(ContextID);
    else
        mem = cmsOpenIOhandlerFromMem(ContextID, Buffer, dwBufferLen, "w");
 
    if (!mem) return 0;
 
    dwBytesUsed =  cmsGetPostScriptColorResource(ContextID, cmsPS_RESOURCE_CSA, hProfile, Intent, dwFlags, mem);
 
    // Get rid of memory stream
    cmsCloseIOhandler(mem);
 
    return dwBytesUsed;
 
}

V1020 The function exited without calling the 'EmitSafeGuardEnd' function. Check lines: 848, 815.