//
//  Little cms
//  Copyright (C) 1998-2007 Marti Maria
//
// 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 "lcms.h"


// Transformations stuff
// -----------------------------------------------------------------------


// Interface

cmsHTRANSFORM LCMSEXPORT cmsCreateTransform(cmsHPROFILE Input,
                                       DWORD InputFormat,
                                       cmsHPROFILE Output,
                                       DWORD OutputFormat,
                                       int Intent,
                                       DWORD dwFlags);

cmsHTRANSFORM LCMSEXPORT cmsCreateProofingTransform(cmsHPROFILE Input,
                                               DWORD InputFormat,
                                               cmsHPROFILE Output,
                                               DWORD OutputFormat,
                                               cmsHPROFILE Proofing,
                                               int Intent,
                                               int ProofingIntent,
                                               DWORD dwFlags);


void         LCMSEXPORT cmsDeleteTransform(cmsHTRANSFORM hTransform);

void         LCMSEXPORT cmsDoTransform(cmsHTRANSFORM Transform,
                                  LPVOID InputBuffer,
                                  LPVOID OutputBuffer, unsigned int Size);

void         LCMSEXPORT cmsGetAlarmCodes(int *r, int *g, int *b);
void         LCMSEXPORT cmsSetAlarmCodes(int r, int g, int b);
LCMSBOOL     LCMSEXPORT cmsIsIntentSupported(cmsHPROFILE hProfile,
                                                int Intent, int UsedDirection);

// -------------------------------------------------------------------------


// Alarm RGB codes

static WORD AlarmR = 0x8fff, AlarmG = 0x8fff, AlarmB = 0x8fff;

// Tag tables, soted by intents

static icTagSignature Device2PCS[] = {icSigAToB0Tag,       // Perceptual
                                      icSigAToB1Tag,       // Relative colorimetric
                                      icSigAToB2Tag,       // Saturation
                                      icSigAToB1Tag };     // Absolute colorimetric
                                                           // (Relative/WhitePoint)

static icTagSignature PCS2Device[] = {icSigBToA0Tag,       // Perceptual
                                      icSigBToA1Tag,       // Relative colorimetric
                                      icSigBToA2Tag,       // Saturation
                                      icSigBToA1Tag };     // Absolute colorimetric
                                                           // (Relative/WhitePoint)


static icTagSignature Preview[]    = {icSigPreview0Tag,
                                      icSigPreview1Tag,
                                      icSigPreview2Tag,
                                      icSigPreview1Tag };



static volatile double GlobalAdaptationState = 0;

// --------------------------------Stages--------------------------------------

// Following routines does implement several kind of steps inside 
// transform. On building the transform, code chooses adequate.


// From Shaper-Matrix to PCS

static
void ShaperMatrixToPCS(struct _cmstransform_struct *p,
                     WORD In[3], WORD Out[3])
{
       cmsEvalMatShaper(p -> InMatShaper, In, Out);
}

// From LUT to PCS

static
void LUTtoPCS(struct _cmstransform_struct *p,
                     WORD In[], WORD Out[3])
{
       cmsEvalLUT(p -> Device2PCS, In, Out);
}

// From indexed named color to PCS 

static
void NC2toPCS(struct _cmstransform_struct *p,
                     WORD In[], WORD Out[3])
{
    int index = In[0];

    if (index >= p ->NamedColorList-> nColors)
        cmsSignalError(LCMS_ERRC_WARNING, "Color %d out of range", index);
    else
        CopyMemory(Out, p ->NamedColorList->List[index].PCS, 3 * sizeof(WORD));
}

// From PCS to Shaper-Matrix

static
void PCStoShaperMatrix(struct _cmstransform_struct *p,
                     WORD In[3], WORD Out[3])
{
       cmsEvalMatShaper(p -> OutMatShaper, In, Out);
}

// From PCS to LUT

static
void PCStoLUT(struct _cmstransform_struct *p,
                     WORD In[3], WORD Out[])
{
       cmsEvalLUT(p -> PCS2Device, In, Out);
}




// ----------------------- TRANSFORMATIONS --------------------------


// Inlining some assignations

#define COPY_3CHANS(to, from) { to[0]=from[0]; to[1]=from[1]; to[2]=from[2]; }


// Null transformation, only hold channels

static
void NullXFORM(_LPcmsTRANSFORM p,
                     LPVOID in,
                     LPVOID out, unsigned int Size)
{
       register LPBYTE accum;
       register LPBYTE output;
       WORD wIn[MAXCHANNELS];
       register unsigned int i, n;


       accum  = (LPBYTE) in;
       output = (LPBYTE) out;
       n = Size;                    // Buffer len

       for (i=0; i < n; i++)
       {
       accum = p -> FromInput(p, wIn, accum);
       output = p -> ToOutput(p, wIn, output);
       }

}


// This is the "normal" proofing transform

static
void NormalXFORM(_LPcmsTRANSFORM p,
                     LPVOID in,
                     LPVOID out, unsigned int Size)
{
       register LPBYTE accum;
       register LPBYTE output;
       WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
       WORD wStageABC[3], wPCS[3], wStageLMN[MAXCHANNELS];
       WORD wGamut[1];
       register unsigned int i, n;



       accum  = (LPBYTE) in;
       output = (LPBYTE) out;
       n = Size;                    // Buffer len

       for (i=0; i < n; i++)
       {

       accum = p -> FromInput(p, wIn, accum);

       p -> FromDevice(p, wIn, wStageABC);

       if (p -> Stage1) {

              p -> Stage1(wStageABC, wPCS, &p->m1, &p->of1);

              if (wPCS[0] == 0xFFFF &&
                  wPCS[1] == 0xFFFF &&
                  wPCS[2] == 0xFFFF) {

                     // White cutoff

                     output = p -> ToOutput((_LPcmsTRANSFORM) p,
                                   _cmsWhiteBySpace(cmsGetColorSpace(p -> OutputProfile)),
                                   output);
                     continue;
                     }
              }
       else
              COPY_3CHANS(wPCS, wStageABC);


       if (p->Gamut) {

       // Gamut check, enabled across CLUT

       cmsEvalLUT(p -> Gamut, wPCS, wGamut);
       
       if (wGamut[0] >= 1) {              
                      
              wOut[0] = AlarmR;          // Gamut alarm
              wOut[1] = AlarmG;
              wOut[2] = AlarmB;
              wOut[3] = 0;

              output = p -> ToOutput((_LPcmsTRANSFORM)p, wOut, output);
              continue;
              }
       }

       if (p -> Preview)
       {
              WORD wPreview[3];    // PCS

              cmsEvalLUT(p -> Preview, wPCS, wPreview);
              COPY_3CHANS(wPCS, wPreview);
       }

       if (p -> Stage2) {

              p -> Stage2(wPCS, wStageLMN, &p->m2, &p->of2);
           
              if (wPCS[0] == 0xFFFF &&
                  wPCS[1] == 0xFFFF &&
                  wPCS[2] == 0xFFFF) {

                     // White cutoff

                     output = p -> ToOutput((_LPcmsTRANSFORM)p,
                                   _cmsWhiteBySpace(cmsGetColorSpace(p -> OutputProfile)),
                                   output);

                     continue;
                     }

              }
       else
              COPY_3CHANS(wStageLMN, wPCS);

       // Here wOut may come as MAXCHANNELS channels

       p -> ToDevice(p, wStageLMN, wOut);

       output = p -> ToOutput((_LPcmsTRANSFORM)p, wOut, output);
       }
}

// Using precalculated LUT

static
void PrecalculatedXFORM(_LPcmsTRANSFORM p,
                     LPVOID in,
                     LPVOID out, unsigned int Size)
{
       register LPBYTE accum;
       register LPBYTE output;
       WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
       unsigned int i, n;
       

       accum  = (LPBYTE) in;
       output = (LPBYTE) out;
       n = Size;                    // Buffer len
      

       for (i=0; i < n; i++) {

         accum = p -> FromInput(p, wIn, accum);

         // Try to speedup things on plain devicelinks  
         
         if (p ->DeviceLink ->wFlags == LUT_HAS3DGRID) {

           p ->DeviceLink ->CLut16params.Interp3D(wIn, wOut, 
                                    p ->DeviceLink -> T, 
                                    &p ->DeviceLink -> CLut16params);
         }
         else      
            cmsEvalLUT(p -> DeviceLink, wIn, wOut);
      

          output = p -> ToOutput(p, wOut, output);
       }
}

// Auxiliar: Handle precalculated gamut check

static
void TransformOnePixelWithGamutCheck(_LPcmsTRANSFORM p, WORD wIn[], WORD wOut[])
{
    WORD wOutOfGamut;

       cmsEvalLUT(p ->GamutCheck,  wIn, &wOutOfGamut);

       if (wOutOfGamut >= 1) {

              ZeroMemory(wOut, sizeof(WORD) * MAXCHANNELS);

              wOut[0] = AlarmR; 
              wOut[1] = AlarmG; 
              wOut[2] = AlarmB; 
              
       }
       else
            cmsEvalLUT(p -> DeviceLink, wIn, wOut);

}


static
void PrecalculatedXFORMGamutCheck(_LPcmsTRANSFORM p,
                                  LPVOID in,
                                  LPVOID out, unsigned int Size)
{
       register LPBYTE accum;
       register LPBYTE output;
       WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
       register unsigned int i, n;


       accum  = (LPBYTE) in;
       output = (LPBYTE) out;
       n = Size;                    // Buffer len

       for (i=0; i < n; i++) {

       accum = p -> FromInput(p, wIn, accum);

       TransformOnePixelWithGamutCheck(p, wIn, wOut);

       output = p -> ToOutput(p, wOut, output);
       }
}



// Using precalculated LUT + Cache

static
void CachedXFORM(_LPcmsTRANSFORM p,
                     LPVOID in,
                     LPVOID out, unsigned int Size)
{
       register LPBYTE accum;
       register LPBYTE output;
       WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
       register unsigned int i, n;
       WORD CacheIn[MAXCHANNELS], CacheOut[MAXCHANNELS];


       accum  = (LPBYTE) in;
       output = (LPBYTE) out;
       n = Size;                    // Buffer len

       // Empty buffers for quick memcmp

       ZeroMemory(wIn,  sizeof(WORD) * MAXCHANNELS);
       ZeroMemory(wOut, sizeof(WORD) * MAXCHANNELS);


       LCMS_READ_LOCK(&p ->rwlock);
           CopyMemory(CacheIn,  p ->CacheIn, sizeof(WORD) * MAXCHANNELS);
           CopyMemory(CacheOut, p ->CacheOut, sizeof(WORD) * MAXCHANNELS);
       LCMS_UNLOCK(&p ->rwlock);

       for (i=0; i < n; i++) {

       accum = p -> FromInput(p, wIn, accum);

       
       if (memcmp(wIn, CacheIn, sizeof(WORD) * MAXCHANNELS) == 0) {

            CopyMemory(wOut, CacheOut, sizeof(WORD) * MAXCHANNELS);
       }
       else {
            
            // Try to speedup things on plain devicelinks  

             if (p ->DeviceLink ->wFlags == LUT_HAS3DGRID) {

             p ->DeviceLink ->CLut16params.Interp3D(wIn, wOut, 
                                    p ->DeviceLink -> T, 
                                    &p ->DeviceLink -> CLut16params);
             }
             else      
                  cmsEvalLUT(p -> DeviceLink, wIn, wOut);
            

            CopyMemory(CacheIn,  wIn,  sizeof(WORD) * MAXCHANNELS);
            CopyMemory(CacheOut, wOut, sizeof(WORD) * MAXCHANNELS);
       }
       
       output = p -> ToOutput(p, wOut, output);
       }

       
       LCMS_WRITE_LOCK(&p ->rwlock);
           CopyMemory(p->CacheIn,  CacheIn, sizeof(WORD) * MAXCHANNELS);
           CopyMemory(p->CacheOut, CacheOut, sizeof(WORD) * MAXCHANNELS);
       LCMS_UNLOCK(&p ->rwlock);

}



// Using precalculated LUT + Cache

static
void CachedXFORMGamutCheck(_LPcmsTRANSFORM p,
                           LPVOID in,
                           LPVOID out, unsigned int Size)
{
       register LPBYTE accum;
       register LPBYTE output;
       WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
       register unsigned int i, n;
       WORD CacheIn[MAXCHANNELS], CacheOut[MAXCHANNELS];


       accum  = (LPBYTE) in;
       output = (LPBYTE) out;
       n = Size;                    // Buffer len

       // Empty buffers for quick memcmp

       ZeroMemory(wIn,  sizeof(WORD) * MAXCHANNELS);
       ZeroMemory(wOut, sizeof(WORD) * MAXCHANNELS);

       LCMS_READ_LOCK(&p ->rwlock);
           CopyMemory(CacheIn,  p ->CacheIn, sizeof(WORD) * MAXCHANNELS);
           CopyMemory(CacheOut, p ->CacheOut, sizeof(WORD) * MAXCHANNELS);
       LCMS_UNLOCK(&p ->rwlock);


       for (i=0; i < n; i++) {

       accum = p -> FromInput(p, wIn, accum);
       
       if (memcmp(wIn, CacheIn, sizeof(WORD) * MAXCHANNELS) == 0) {

            CopyMemory(wOut, CacheOut, sizeof(WORD) * MAXCHANNELS);
       }
       else {
            
            TransformOnePixelWithGamutCheck(p, wIn, wOut);

            CopyMemory(CacheIn, wIn, sizeof(WORD) * MAXCHANNELS);
            CopyMemory(CacheOut, wOut, sizeof(WORD) * MAXCHANNELS);
       }

       output = p -> ToOutput(p, wOut, output);
       }

        LCMS_WRITE_LOCK(&p ->rwlock);
           CopyMemory(p->CacheIn,  CacheIn, sizeof(WORD) * MAXCHANNELS);
           CopyMemory(p->CacheOut, CacheOut, sizeof(WORD) * MAXCHANNELS);
        LCMS_UNLOCK(&p ->rwlock);
}


// Using smelted Matrix/Shaper

static
void MatrixShaperXFORM(_LPcmsTRANSFORM p,
                     LPVOID in,
                     LPVOID out, unsigned int Size)
{
       register LPBYTE accum;
       register LPBYTE output;
       WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
       register unsigned int i, n;


       accum  = (LPBYTE) in;
       output = (LPBYTE) out;
       n = Size;                    // Buffer len

       for (i=0; i < n; i++)
       {
       accum = p -> FromInput(p, wIn, accum);
       cmsEvalMatShaper(p -> SmeltMatShaper, wIn, wOut);
       output = p -> ToOutput(p, wOut, output);
       }
}


// Using Named color input table

static
void NC2deviceXform(_LPcmsTRANSFORM p,
                     LPVOID in,
                     LPVOID out, unsigned int Size)
{

       register LPBYTE accum;
       register LPBYTE output;
       WORD wIn[MAXCHANNELS], wOut[MAXCHANNELS];
       register unsigned int i;


       accum  = (LPBYTE) in;
       output = (LPBYTE) out;

       for (i=0; i < Size; i++) {

       accum = p -> FromInput(p, wIn, accum);      
       CopyMemory(wOut, p ->NamedColorList->List[wIn[0]].DeviceColorant, sizeof(WORD) * MAXCHANNELS);
       output = p -> ToOutput(p, wOut, output);
       }
        
}



// --------------------------------------------------------------------------
// Build a LUT based on shape-matrix method.


// Some non-conformant gray profiles are using kTCR as L*,
// this function converts the curve to XYZ PCS.

static
void FromLstarToXYZ(LPGAMMATABLE g, LPGAMMATABLE gxyz[3])
{
    int i;
    int nPoints = 4096;
    cmsCIELab Lab;
    cmsCIEXYZ XYZ;
    L16PARAMS L16;

    // Setup interpolation across origin
    cmsCalcL16Params(g ->nEntries, &L16);

    // Allocate curves
    gxyz[0] = cmsAllocGamma(nPoints);
    gxyz[1] = cmsAllocGamma(nPoints);
    gxyz[2] = cmsAllocGamma(nPoints);

    // Transport from Lab to XYZ

    for (i=0; i < nPoints; i++) {

        WORD val = _cmsQuantizeVal(i, nPoints);
        WORD w   = cmsLinearInterpLUT16(val, g->GammaTable, &L16);

        Lab.L = ((double) 100.0 * w ) / 65535.0;
        Lab.a = Lab.b = 0;

        cmsLab2XYZ(NULL, &XYZ, &Lab);

        // Should be same curve
        gxyz[0] ->GammaTable[i] = (WORD) floor((65535.0 * XYZ.X) / D50X + 0.5);
        gxyz[1] ->GammaTable[i] = (WORD) floor((65535.0 * XYZ.Y) / D50Y + 0.5);
        gxyz[2] ->GammaTable[i] = (WORD) floor((65535.0 * XYZ.Z) / D50Z + 0.5);
    }
}

// Monochrome version

static
LPMATSHAPER cmsBuildGrayInputMatrixShaper(cmsHPROFILE hProfile)
{
       cmsCIEXYZ Illuminant;
       LPGAMMATABLE GrayTRC, Shapes[3];
       LPMATSHAPER MatShaper;
       MAT3 Scale;

       GrayTRC = cmsReadICCGamma(hProfile, icSigGrayTRCTag);        // Y
       if (GrayTRC == NULL) return NULL;

       cmsTakeIluminant(&Illuminant, hProfile);

       if (cmsGetPCS(hProfile) == icSigLabData) {

                // Fixup for Lab monochrome
                FromLstarToXYZ(GrayTRC, Shapes);
       }
       else  {
                Shapes[0] = cmsDupGamma(GrayTRC);
                Shapes[1] = cmsDupGamma(GrayTRC);
                Shapes[2] = cmsDupGamma(GrayTRC); 
       }
       
       if (!Shapes[0] || !Shapes[1] || !Shapes[2])
              return NULL;
      
       cmsFreeGamma(GrayTRC);

       // R=G=B as precondition

       VEC3init(&Scale.v[0], Illuminant.X/3,  Illuminant.X/3,  Illuminant.X/3);
       VEC3init(&Scale.v[1], Illuminant.Y/3,  Illuminant.Y/3,  Illuminant.Y/3);
       VEC3init(&Scale.v[2], Illuminant.Z/3,  Illuminant.Z/3,  Illuminant.Z/3);
      
       
       MatShaper = cmsAllocMatShaper(&Scale, Shapes, MATSHAPER_INPUT);
       cmsFreeGammaTriple(Shapes);
       return MatShaper;

}


// Monochrome as output

static
LPMATSHAPER cmsBuildGrayOutputMatrixShaper(cmsHPROFILE hProfile)
{
       cmsCIEXYZ Illuminant;
       LPGAMMATABLE GrayTRC, Shapes[3];
       LPMATSHAPER MatShaper;
       MAT3 Scale;
       
       cmsTakeIluminant(&Illuminant, hProfile);

       // That is a special case for non-compliant profiles. 

       if (cmsGetPCS(hProfile) == icSigLabData) {
                
                LPGAMMATABLE Shapes1[3];

                GrayTRC = cmsReadICCGamma(hProfile, icSigGrayTRCTag);
                FromLstarToXYZ(GrayTRC, Shapes1);

                // Reversing must be done after curve translation

                Shapes[0] = cmsReverseGamma(Shapes1[0]->nEntries, Shapes1[0]);
                Shapes[1] = cmsReverseGamma(Shapes1[1]->nEntries, Shapes1[1]);
                Shapes[2] = cmsReverseGamma(Shapes1[2]->nEntries, Shapes1[2]);
                
                cmsFreeGammaTriple(Shapes1);

       }
       else  {

                // Normal case

                GrayTRC = cmsReadICCGammaReversed(hProfile, icSigGrayTRCTag);   // Y

                Shapes[0] = cmsDupGamma(GrayTRC);
                Shapes[1] = cmsDupGamma(GrayTRC);
                Shapes[2] = cmsDupGamma(GrayTRC); 
       }
            
       if (!Shapes[0] || !Shapes[1] || !Shapes[2])
              return NULL;
      
       cmsFreeGamma(GrayTRC);

       VEC3init(&Scale.v[0], 0,  1.0/Illuminant.Y,  0);
       VEC3init(&Scale.v[1], 0,  1.0/Illuminant.Y,  0);
       VEC3init(&Scale.v[2], 0,  1.0/Illuminant.Y,  0);
           
       
       MatShaper = cmsAllocMatShaper(&Scale, Shapes, MATSHAPER_OUTPUT);
       cmsFreeGammaTriple(Shapes);
       return MatShaper;

}



// Input matrix, only in XYZ

LPMATSHAPER cmsBuildInputMatrixShaper(cmsHPROFILE InputProfile)
{
       MAT3 DoubleMat;
       LPGAMMATABLE Shapes[3];
       LPMATSHAPER InMatSh;

       // Check if this is a grayscale profile. If so, build
       // appropiate conversion tables. The tables are the PCS
       // iluminant, scaled across GrayTRC

       if (cmsGetColorSpace(InputProfile) == icSigGrayData)
       {             
              return cmsBuildGrayInputMatrixShaper(InputProfile);              
       }

       if (!cmsReadICCMatrixRGB2XYZ(&DoubleMat, InputProfile))
                     return NULL;

       Shapes[0] = cmsReadICCGamma(InputProfile, icSigRedTRCTag);
       Shapes[1] = cmsReadICCGamma(InputProfile, icSigGreenTRCTag);
       Shapes[2] = cmsReadICCGamma(InputProfile, icSigBlueTRCTag);

       if (!Shapes[0] || !Shapes[1] || !Shapes[2])
                     return NULL;

       InMatSh = cmsAllocMatShaper(&DoubleMat, Shapes, MATSHAPER_INPUT);

       cmsFreeGammaTriple(Shapes);
      
       return InMatSh;
}


// Output style matrix-shaper


LPMATSHAPER cmsBuildOutputMatrixShaper(cmsHPROFILE OutputProfile)
{
       MAT3 DoubleMat, DoubleInv;
       LPGAMMATABLE InverseShapes[3];
       LPMATSHAPER OutMatSh;

       

       if (cmsGetColorSpace(OutputProfile) == icSigGrayData)
       {              
              return cmsBuildGrayOutputMatrixShaper(OutputProfile);              
       }


       if (!cmsReadICCMatrixRGB2XYZ(&DoubleMat, OutputProfile))
                     return NULL;

       if (MAT3inverse(&DoubleMat, &DoubleInv) < 0)
              return NULL;

     
       InverseShapes[0] = cmsReadICCGammaReversed(OutputProfile, icSigRedTRCTag);
       InverseShapes[1] = cmsReadICCGammaReversed(OutputProfile, icSigGreenTRCTag);
       InverseShapes[2] = cmsReadICCGammaReversed(OutputProfile, icSigBlueTRCTag);
       
       if (InverseShapes[0] == NULL ||
           InverseShapes[1] == NULL ||
           InverseShapes[2] == NULL) return NULL;

       OutMatSh = cmsAllocMatShaper(&DoubleInv, InverseShapes, MATSHAPER_OUTPUT);

       cmsFreeGammaTriple(InverseShapes);
       
       return OutMatSh;
}



// This function builds a transform matrix chaining parameters

static
LCMSBOOL cmsBuildSmeltMatShaper(_LPcmsTRANSFORM p)
{
       MAT3 From, To, ToInv, Transfer;
       LPGAMMATABLE In[3], InverseOut[3];
       
        
       if (!cmsReadICCMatrixRGB2XYZ(&From, p -> InputProfile))
                     return FALSE;


       if (!cmsReadICCMatrixRGB2XYZ(&To, p -> OutputProfile))
                     return FALSE;

       // invert dest
       
       if (MAT3inverse(&To, &ToInv) < 0)
                        return FALSE;

       // Multiply
        MAT3per(&Transfer, &ToInv, &From); 
    
            
        // Read gamma curves

        In[0] = cmsReadICCGamma(p -> InputProfile, icSigRedTRCTag);
        In[1] = cmsReadICCGamma(p -> InputProfile, icSigGreenTRCTag);
        In[2] = cmsReadICCGamma(p -> InputProfile, icSigBlueTRCTag);

        if (!In[0] || !In[1] || !In[2])
                     return FALSE;
            

        InverseOut[0] = cmsReadICCGammaReversed(p -> OutputProfile, icSigRedTRCTag);
        InverseOut[1] = cmsReadICCGammaReversed(p -> OutputProfile, icSigGreenTRCTag);
        InverseOut[2] = cmsReadICCGammaReversed(p -> OutputProfile, icSigBlueTRCTag);

        if (!InverseOut[0] || !InverseOut[1] || !InverseOut[2]) {
                     cmsFreeGammaTriple(In); 
                     return FALSE;
        }

        p -> SmeltMatShaper = cmsAllocMatShaper2(&Transfer, In, InverseOut, MATSHAPER_ALLSMELTED);

        cmsFreeGammaTriple(In);
        cmsFreeGammaTriple(InverseOut);
        
        return (p -> SmeltMatShaper != NULL);
}




// Conversion between PCS ------------------------------------------

// Identifies intent archieved by LUT

static
int GetPhase(cmsHPROFILE hProfile)
{       
       switch (cmsGetPCS(hProfile)) {

       case icSigXYZData: return XYZRel;

       case icSigLabData: return LabRel;

       default:  cmsSignalError(LCMS_ERRC_ABORTED, "Invalid PCS");
       }

       return XYZRel;
}




static
void TakeConversionRoutines(_LPcmsTRANSFORM p, int DoBPC)
{
       cmsCIEXYZ BlackPointIn, WhitePointIn, IlluminantIn;
       cmsCIEXYZ BlackPointOut, WhitePointOut, IlluminantOut;
       cmsCIEXYZ BlackPointProof, WhitePointProof, IlluminantProof;
       MAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
       MAT3 ChromaticAdaptationMatrixProof;


       cmsTakeIluminant(&IlluminantIn,        p -> InputProfile);
       cmsTakeMediaWhitePoint(&WhitePointIn,  p -> InputProfile);
       cmsTakeMediaBlackPoint(&BlackPointIn,  p -> InputProfile);
       cmsReadChromaticAdaptationMatrix(&ChromaticAdaptationMatrixIn, p -> InputProfile);

       cmsTakeIluminant(&IlluminantOut,        p -> OutputProfile);
       cmsTakeMediaWhitePoint(&WhitePointOut,  p -> OutputProfile);
       cmsTakeMediaBlackPoint(&BlackPointOut,  p -> OutputProfile);
       cmsReadChromaticAdaptationMatrix(&ChromaticAdaptationMatrixOut, p -> OutputProfile);

            
       if (p -> Preview == NULL && p ->Gamut == NULL)     // Non-proofing
       {            
            if (p ->Intent == INTENT_PERCEPTUAL ||
                p ->Intent == INTENT_SATURATION) {


                    // For v4 profiles, Perceptual PCS has a reference black point
                    // which v2 profiles should scale to.

                    if ((cmsGetProfileICCversion(p ->InputProfile) >= 0x4000000) ||
                        (cmsGetProfileICCversion(p ->OutputProfile) >= 0x4000000)) {

                           DoBPC = TRUE;
                    }
            }

            // Black point compensation does not apply to absolute intent

            if (p ->Intent == INTENT_ABSOLUTE_COLORIMETRIC) 
                            DoBPC = FALSE;

            // Black point compensation does not apply to devicelink profiles
            
            if (cmsGetDeviceClass(p ->InputProfile) == icSigLinkClass)
                            DoBPC = FALSE;

            if (cmsGetDeviceClass(p ->OutputProfile) == icSigLinkClass)
                            DoBPC = FALSE;
            
            if (DoBPC) {
       
                // Detect Black points

                cmsDetectBlackPoint(&BlackPointIn,    p->InputProfile,   p->Intent, 0);
                cmsDetectBlackPoint(&BlackPointOut,   p->OutputProfile,  p->Intent, 0);          

                // If equal black points, then do nothing. This often applies to BP=0

                if (BlackPointIn.X == BlackPointOut.X &&
                    BlackPointIn.Y == BlackPointOut.Y &&
                    BlackPointIn.Z == BlackPointOut.Z) 
                                DoBPC = FALSE;


            }
            
            cmsChooseCnvrt(p -> Intent == INTENT_ABSOLUTE_COLORIMETRIC,

                 p -> Phase1,
                             &BlackPointIn,
                             &WhitePointIn,
                             &IlluminantIn,
                             &ChromaticAdaptationMatrixIn,

                 p -> Phase3,
                             &BlackPointOut,
                             &WhitePointOut,
                             &IlluminantOut,
                             &ChromaticAdaptationMatrixOut,

                 DoBPC,
                 p ->AdaptationState,
                 &p->Stage1,
                 &p->m1, &p->of1);

       }
       else // Proofing
       {

       
       cmsTakeIluminant(&IlluminantProof,        p -> PreviewProfile);
       cmsTakeMediaWhitePoint(&WhitePointProof,  p -> PreviewProfile);
       cmsTakeMediaBlackPoint(&BlackPointProof,  p -> PreviewProfile);
       cmsReadChromaticAdaptationMatrix(&ChromaticAdaptationMatrixProof, p -> PreviewProfile);

       if (DoBPC) {

            cmsDetectBlackPoint(&BlackPointProof, p->PreviewProfile, p->Intent, 0);
            cmsDetectBlackPoint(&BlackPointIn,    p->InputProfile,   p->Intent, 0);
            cmsDetectBlackPoint(&BlackPointOut,   p->OutputProfile,  p->Intent, 0);  
            
            // Reality check

            if (BlackPointIn.X == BlackPointProof.X &&
                BlackPointIn.Y == BlackPointProof.Y &&
                BlackPointIn.Z == BlackPointProof.Z) 
                                DoBPC = FALSE;


       }

       

       cmsChooseCnvrt(p -> Intent == INTENT_ABSOLUTE_COLORIMETRIC,

                 p -> Phase1,
                             &BlackPointIn,
                             &WhitePointIn,
                             &IlluminantIn,
                             &ChromaticAdaptationMatrixIn,

                 p -> Phase2,
                             &BlackPointProof,
                             &WhitePointProof,
                             &IlluminantProof,
                             &ChromaticAdaptationMatrixProof,
                 DoBPC,
                 p ->AdaptationState,
                 &p->Stage1,
                 &p->m1, &p->of1);

       cmsChooseCnvrt(p -> ProofIntent == INTENT_ABSOLUTE_COLORIMETRIC,

                 p -> Phase2,
                             &BlackPointProof,
                             &WhitePointProof,
                             &IlluminantProof,
                             &ChromaticAdaptationMatrixProof,

                 p -> Phase3,
                             &BlackPointOut,
                             &WhitePointOut,
                             &IlluminantOut,
                             &ChromaticAdaptationMatrixOut,
                 0,
                 0.0,
                 &p->Stage2,
                 &p->m2, &p->of2);
       }

}


// Check colorspace

static
LCMSBOOL IsProperColorSpace(cmsHPROFILE hProfile, DWORD dwFormat, LCMSBOOL lUsePCS)
{
       int Space = T_COLORSPACE(dwFormat);

       if (Space == PT_ANY) return TRUE;

       if (lUsePCS)
           return (Space == _cmsLCMScolorSpace(cmsGetPCS(hProfile)));
       else
           return (Space == _cmsLCMScolorSpace(cmsGetColorSpace(hProfile)));
}


// Auxiliary: allocate transform struct and set to defaults

static
_LPcmsTRANSFORM AllocEmptyTransform(void)
{
    // Allocate needed memory

    _LPcmsTRANSFORM p = (_LPcmsTRANSFORM) _cmsMalloc(sizeof(_cmsTRANSFORM));
    if (!p) {

          cmsSignalError(LCMS_ERRC_ABORTED, "cmsCreateTransform: _cmsMalloc() failed");
          return NULL;
    }

    ZeroMemory(p, sizeof(_cmsTRANSFORM));

    // Initialize default methods

    p -> xform          = NULL;
    p -> Intent         = INTENT_PERCEPTUAL;
    p -> ProofIntent    = INTENT_ABSOLUTE_COLORIMETRIC;
    p -> DoGamutCheck   = FALSE;
    p -> InputProfile   = NULL;
    p -> OutputProfile  = NULL;
    p -> PreviewProfile = NULL;
    p -> Preview        = NULL;
    p -> Gamut          = NULL;
    p -> DeviceLink     = NULL;
    p -> InMatShaper    = NULL;
    p -> OutMatShaper   = NULL;
    p -> SmeltMatShaper = NULL;
    p -> NamedColorList = NULL;
    p -> EntryColorSpace = (icColorSpaceSignature) 0;
    p -> ExitColorSpace  = (icColorSpaceSignature) 0;
    p -> AdaptationState = GlobalAdaptationState;

    LCMS_CREATE_LOCK(&p->rwlock);

    return p;
}


// Identify whatever a transform is to be cached

static
void SetPrecalculatedTransform(_LPcmsTRANSFORM p)
{
    if ((p->dwOriginalFlags & cmsFLAGS_GAMUTCHECK) && p ->GamutCheck != NULL) {
        
        p -> xform = PrecalculatedXFORMGamutCheck;
        
        if (!(p->dwOriginalFlags & cmsFLAGS_NOTCACHE)) {
            
            ZeroMemory(p ->CacheIn, sizeof(WORD) * MAXCHANNELS);
            TransformOnePixelWithGamutCheck(p, p->CacheIn, p ->CacheOut);        
            p ->xform = CachedXFORMGamutCheck;              
        }
        
    }
    else {
        
        p -> xform = PrecalculatedXFORM;
        
        if (!(p->dwOriginalFlags & cmsFLAGS_NOTCACHE)) {
            
            ZeroMemory(p ->CacheIn, sizeof(WORD) * MAXCHANNELS);
            cmsEvalLUT(p ->DeviceLink, p->CacheIn, p ->CacheOut);        
            p ->xform = CachedXFORM;        
        }
    }
}


// Transform is identified as device-link
static 
cmsHPROFILE CreateDeviceLinkTransform(_LPcmsTRANSFORM p)
{
    
    if (!IsProperColorSpace(p->InputProfile, p->InputFormat, FALSE)) {
        cmsSignalError(LCMS_ERRC_ABORTED, "Device link is operating on wrong colorspace on input");
        return NULL;
    }

    if (!IsProperColorSpace(p->InputProfile, p->OutputFormat, TRUE)) {
        cmsSignalError(LCMS_ERRC_ABORTED, "Device link is operating on wrong colorspace on output");
        return NULL;
    }

    // Device link does only have AToB0Tag (ICC-Spec 1998/09)

    p->DeviceLink = cmsReadICCLut(p->InputProfile, icSigAToB0Tag);

    if (!p->DeviceLink) {

         cmsSignalError(LCMS_ERRC_ABORTED, "Noncompliant device-link profile");
         cmsDeleteTransform((cmsHTRANSFORM) p);
         return NULL;
         }

    if (p ->PreviewProfile != NULL) {
            cmsSignalError(LCMS_ERRC_WARNING, "Proofing not supported on device link transforms");
    }

    if (p ->OutputProfile != NULL) {
            cmsSignalError(LCMS_ERRC_WARNING, "Output profile should be NULL, since this is a device-link transform");
    }

    p -> Phase1 = -1;
    p -> Phase2 = -1;
    p -> Phase3 = -1;

    SetPrecalculatedTransform(p);
    
    p -> EntryColorSpace = cmsGetColorSpace(p -> InputProfile);
    p -> ExitColorSpace  = cmsGetPCS(p -> InputProfile);
    
    if (p ->EntryColorSpace == icSigRgbData ||
        p ->EntryColorSpace == icSigCmyData) {
                   
                    p->DeviceLink -> CLut16params.Interp3D = cmsTetrahedralInterp16;
    }
               
    // Precalculated device-link profile is ready
    return (cmsHTRANSFORM) p;
}


// Transform that includes proofing
static
void CreateProof(_LPcmsTRANSFORM p, icTagSignature *ToTagPtr)

{
    icTagSignature ProofTag;
   
    if (p -> dwOriginalFlags & cmsFLAGS_SOFTPROOFING) {

      // Apr-15, 2002 - Too much profiles does have bogus content
      // on preview tag, so I do compute it by my own.

      p -> Preview = _cmsComputeSoftProofLUT(p ->PreviewProfile, p ->Intent); 
      p -> Phase2  = LabRel;

      // That's a proofing transfor, so use second intent for output.

      *ToTagPtr  = PCS2Device[p->ProofIntent];

      if (p -> Preview == NULL) {
          
        ProofTag = Preview[p -> Intent];

        if (!cmsIsTag(p ->PreviewProfile,  ProofTag)) {

            ProofTag = Preview[0];
            if (!cmsIsTag(p ->PreviewProfile,  ProofTag))
                            ProofTag = (icTagSignature)0;
        }

        if (ProofTag) {

             p -> Preview = cmsReadICCLut(p ->PreviewProfile, ProofTag);
             p -> Phase2 = GetPhase(p ->PreviewProfile);
                         
        }
        else
             {
             p -> Preview = NULL;
             p ->PreviewProfile = NULL;
             cmsSignalError(LCMS_ERRC_WARNING, "Sorry, the proof profile has not previewing capabilities");
             }
      }
                
    }


    // Aug-31, 2001 - Too much profiles does have bogus content
    // on gamut tag, so I do compute it by my own.

    if ((p -> dwOriginalFlags & cmsFLAGS_GAMUTCHECK) && (p -> dwOriginalFlags & cmsFLAGS_NOTPRECALC)) {

                   
             p -> Gamut = _cmsComputeGamutLUT(p->PreviewProfile, p ->Intent);  
             p -> Phase2  = LabRel;

             if (p -> Gamut == NULL) {

                // Profile goes only in one direction... try to see 
                // if profile has the tag, and use it, no matter it
                // could be bogus. This is the last chance!

                if (cmsIsTag(p ->PreviewProfile, icSigGamutTag)) {

                    p -> Gamut = cmsReadICCLut(p ->PreviewProfile, icSigGamutTag);
                    
                    }
                    else   {
                            
                     // Nope, cannot be done.

                     cmsSignalError(LCMS_ERRC_WARNING, "Sorry, the proof profile has not gamut checking capabilities");
                     p -> Gamut = NULL;
                    }
             }

      }

}

// Choose the adequate transform routine

static
_LPcmsTRANSFORM PickTransformRoutine(_LPcmsTRANSFORM p,                                                         
                                    icTagSignature *FromTagPtr,
                                    icTagSignature *ToTagPtr)
{

       
       

        // Is a named color profile?
        if (cmsGetDeviceClass(p->InputProfile) == icSigNamedColorClass) {

                  // Yes, and used as input
                  p ->FromDevice = NC2toPCS;
        }
        else {
                // Can we optimize matrix-shaper only transform?

                   if ((*FromTagPtr == 0) && 
                       (*ToTagPtr == 0) && 
                       (!p->PreviewProfile) && 
                       (p -> Intent != INTENT_ABSOLUTE_COLORIMETRIC) && 
                       (p -> EntryColorSpace == icSigRgbData) && 
                       (p -> ExitColorSpace == icSigRgbData)  &&
                       !(p -> dwOriginalFlags & cmsFLAGS_BLACKPOINTCOMPENSATION)) {

                          // Yes... try to smelt matrix-shapers
                          p -> xform = MatrixShaperXFORM;
                          p -> dwOriginalFlags |= cmsFLAGS_NOTPRECALC;

                          if (!cmsBuildSmeltMatShaper(p))
                          {
                                 cmsSignalError(LCMS_ERRC_ABORTED, "unable to smelt shaper-matrix, required tags missing");               
                                 return NULL;
                          }

                          p -> Phase1 = p -> Phase3 = XYZRel;
                          return p;

                }

                // No, is a transform involving LUT

                if (*FromTagPtr != 0) {

                     p -> FromDevice = LUTtoPCS;
                     p -> Device2PCS = cmsReadICCLut(p -> InputProfile, *FromTagPtr);
                     if (!p -> Device2PCS) {

                            cmsSignalError(LCMS_ERRC_ABORTED, "profile is unsuitable for input");                            
                            return NULL;
                            }

              }
              else
              {
                     p -> FromDevice = ShaperMatrixToPCS;
                     p -> InMatShaper = cmsBuildInputMatrixShaper(p -> InputProfile);

                     if (!p ->InMatShaper) {
                            cmsSignalError(LCMS_ERRC_ABORTED, "profile is unsuitable for input");                            
                            return NULL;
                            }

                     p -> Phase1 = XYZRel;

              }
              }

              if (*ToTagPtr != 0) {

                     p -> ToDevice = PCStoLUT;
                     p -> PCS2Device = cmsReadICCLut(p -> OutputProfile, *ToTagPtr);
                     if (!p -> PCS2Device) {
                            cmsSignalError(LCMS_ERRC_ABORTED, "profile is unsuitable for output");                            
                            return NULL;
                            }

                     }
              else
              {
                     p -> ToDevice = PCStoShaperMatrix;
                     p -> OutMatShaper = cmsBuildOutputMatrixShaper(p->OutputProfile);

                     if (!p -> OutMatShaper) {
                            cmsSignalError(LCMS_ERRC_ABORTED, "profile is unsuitable for output");                            
                            return NULL;
                            }
                     p -> Phase3 = XYZRel;
                     
              }
       

       return p;
}




// Create a transform.

cmsHTRANSFORM LCMSEXPORT cmsCreateProofingTransform(cmsHPROFILE InputProfile,
                                               DWORD InputFormat,
                                               cmsHPROFILE OutputProfile,
                                               DWORD OutputFormat,
                                               cmsHPROFILE ProofingProfile,
                                               int nIntent,
                                               int ProofingIntent,
                                               DWORD dwFlags)

{
       _LPcmsTRANSFORM p;
       icTagSignature FromTag;
       icTagSignature ToTag;
       
       if (nIntent < 0 || nIntent > 3 ||
           ProofingIntent < 0 || ProofingIntent > 3) {

            cmsSignalError(LCMS_ERRC_ABORTED, "cmsCreateTransform: intent mismatch");
            return NULL;
       }

       p = AllocEmptyTransform();
       if (p == NULL) return NULL;

       p -> xform           = NormalXFORM;
       p -> Intent          = nIntent;
       p -> ProofIntent     = ProofingIntent;
       p -> DoGamutCheck    = FALSE;
       p -> InputProfile    = InputProfile;
       p -> OutputProfile   = OutputProfile;
       p -> PreviewProfile  = ProofingProfile;            
       p -> InputFormat     = InputFormat;
       p -> OutputFormat    = OutputFormat;
       p -> dwOriginalFlags = dwFlags;

       p -> lInputV4Lab     = p ->lOutputV4Lab = FALSE;


       p -> FromInput = _cmsIdentifyInputFormat(p, InputFormat);
       p -> ToOutput  = _cmsIdentifyOutputFormat(p, OutputFormat);

       // Null transform can be done without profiles
       if ((p->dwOriginalFlags & cmsFLAGS_NULLTRANSFORM) ||
                        ((InputProfile == NULL) &&
                         (OutputProfile == NULL))) {

            p -> xform = NullXFORM;
            return (cmsHTRANSFORM) p;
       }

       // From here we need at least one input profile
       if (InputProfile == NULL) {
          
          cmsSignalError(LCMS_ERRC_ABORTED, "Input profile cannot be NULL!");
          cmsDeleteTransform((cmsHTRANSFORM) p);
          return NULL;
       }


       //  Device link are means to store precalculated transform grids.
       if (cmsGetDeviceClass(InputProfile) == icSigLinkClass) {

            return CreateDeviceLinkTransform(p);
       }
         
       if (!IsProperColorSpace(InputProfile, InputFormat, FALSE)) {

              cmsSignalError(LCMS_ERRC_ABORTED, "Input profile is operating on wrong colorspace");
              cmsDeleteTransform((cmsHTRANSFORM) p);
              return NULL;
       }

       p ->EntryColorSpace = cmsGetColorSpace(InputProfile);

       // Oct-21-2002: Added named color transforms
       if (cmsGetDeviceClass(InputProfile) == icSigNamedColorClass) {

        if (p ->NamedColorList == NULL)
                p ->NamedColorList = cmsAllocNamedColorList(0);

        cmsReadICCnamedColorList(p, InputProfile, icSigNamedColor2Tag);
        
        // Special case. If output profile == NULL, then the transform gives
        // device values from named colors.

        if (OutputProfile == NULL) {

            p ->ExitColorSpace = p -> EntryColorSpace;
            p ->xform = NC2deviceXform;
            return (cmsHTRANSFORM) p;
        }

        // Named color doesn't precalc anything
        p -> dwOriginalFlags |= cmsFLAGS_NOTPRECALC;
       }

    
       // From here we need also output profile.
      if (OutputProfile == NULL) {          
          cmsSignalError(LCMS_ERRC_ABORTED, "Output profile cannot be NULL!");
          cmsDeleteTransform((cmsHTRANSFORM) p);
          return NULL;
       }

            
       if (!IsProperColorSpace(OutputProfile, OutputFormat, FALSE)) {
              cmsSignalError(LCMS_ERRC_ABORTED, "Output profile is operating on wrong colorspace");
              cmsDeleteTransform((cmsHTRANSFORM) p);             
              return NULL;
       }

       p -> ExitColorSpace = cmsGetColorSpace(OutputProfile);

       // Named color only on input
       if (cmsGetDeviceClass(OutputProfile) == icSigNamedColorClass) {

           cmsSignalError(LCMS_ERRC_ABORTED, "Named color profiles are not supported as output");
           cmsDeleteTransform((cmsHTRANSFORM) p);
           return NULL;
       }

       p -> Phase1 = GetPhase(InputProfile);
       p -> Phase2 = -1;
       p -> Phase3 = GetPhase(OutputProfile);

       // Try to locate a LUT

       FromTag  = Device2PCS[nIntent];
       ToTag    = PCS2Device[nIntent];

       if (!cmsIsTag(InputProfile, FromTag)) {

              FromTag = Device2PCS[0];

              if (!cmsIsTag(InputProfile,  FromTag)) {
                            FromTag = (icTagSignature)0;
              }
       }

       // If proofing is needed, add required tags/parameters
       if (ProofingProfile) 
           CreateProof(p, &ToTag);                       
       

       if (!cmsIsTag(OutputProfile,  ToTag)) {

           ToTag = PCS2Device[0];

           // 12-Dec-2003, Abstract profiles can be placed as output and still using AToB0          
           if (cmsGetDeviceClass(OutputProfile) == icSigAbstractClass) {
           
                    if (!cmsIsTag(OutputProfile,  ToTag)) {
                                ToTag = (icTagSignature) icSigAToB0Tag;
                    }           
           }
                
           if (!cmsIsTag(OutputProfile,  ToTag))
                            ToTag = (icTagSignature)0;
       }


       if (p-> dwOriginalFlags & cmsFLAGS_MATRIXINPUT)
              FromTag = (icTagSignature)0;

       if (p -> dwOriginalFlags & cmsFLAGS_MATRIXOUTPUT)
              ToTag = (icTagSignature)0;

              
     
       if (PickTransformRoutine(p, &FromTag, &ToTag) == NULL) {

          cmsDeleteTransform((cmsHTRANSFORM) p);
          return NULL;

       }

       TakeConversionRoutines(p, dwFlags & cmsFLAGS_BLACKPOINTCOMPENSATION);

       if (!(p -> dwOriginalFlags & cmsFLAGS_NOTPRECALC)) {

               LPLUT DeviceLink;   
               LPLUT GamutCheck = NULL;
               

               if (p ->EntryColorSpace == icSigCmykData &&
                   p ->ExitColorSpace == icSigCmykData &&
                   (dwFlags & cmsFLAGS_PRESERVEBLACK)) {

                    DeviceLink = _cmsPrecalculateBlackPreservingDeviceLink((cmsHTRANSFORM) p, dwFlags);

                    // Cannot be done at all?
                    if (DeviceLink == NULL) 
                            DeviceLink = _cmsPrecalculateDeviceLink((cmsHTRANSFORM) p, dwFlags);

               }   
               else {

                    DeviceLink = _cmsPrecalculateDeviceLink((cmsHTRANSFORM) p, dwFlags);
               }
                
               // Allow to specify cmsFLAGS_GAMUTCHECK, even if no proofing profile is given               
               if ((p ->PreviewProfile != NULL) && (p -> dwOriginalFlags & cmsFLAGS_GAMUTCHECK)) {

                   GamutCheck = _cmsPrecalculateGamutCheck((cmsHTRANSFORM) p);
               }

               // If input colorspace is Rgb, Cmy, then use tetrahedral interpolation
               // for speed reasons (it only works well on spaces on Luma is diagonal, and 
               // not if luma is in separate channel)
               if (p ->EntryColorSpace == icSigRgbData ||
                   p ->EntryColorSpace == icSigCmyData) {
                   
                   
                    cmsCalcCLUT16ParamsEx(DeviceLink->CLut16params.nSamples,
                                          DeviceLink->CLut16params.nInputs,
                                          DeviceLink->CLut16params.nOutputs, 
                                          TRUE, &DeviceLink->CLut16params);
                    
               }
               
               // If this is a 8-bit transform, optimize LUT further.
                                           
                if ((T_BYTES(InputFormat) == 1) && (T_CHANNELS(InputFormat) == 3)) {

                    DeviceLink = _cmsBlessLUT8(DeviceLink);
                    if (DeviceLink == NULL) return NULL;

                }
                

                p ->GamutCheck = GamutCheck;

                if (DeviceLink) {

                    p ->DeviceLink = DeviceLink;

                    if ((nIntent != INTENT_ABSOLUTE_COLORIMETRIC) && 
                        !(p -> dwOriginalFlags & cmsFLAGS_NOWHITEONWHITEFIXUP))

                            _cmsFixWhiteMisalignment(p);

                }
                else
                {

                     cmsSignalError(LCMS_ERRC_ABORTED,
                                "Cannot precalculate %d->%d channels transform!",
                                T_CHANNELS(InputFormat), T_CHANNELS(OutputFormat));

                     cmsDeleteTransform(p);
                     return NULL;
                }


              SetPrecalculatedTransform(p);
              

       }

       // Re-Identify formats
       p -> FromInput = _cmsIdentifyInputFormat(p, InputFormat);
       p -> ToOutput  = _cmsIdentifyOutputFormat(p, OutputFormat);

     
       return p;
}


// Wrapper por simpler non-proofing transforms.

cmsHTRANSFORM LCMSEXPORT cmsCreateTransform(cmsHPROFILE Input,
                                       DWORD InputFormat,
                                       cmsHPROFILE Output,
                                       DWORD OutputFormat,
                                       int Intent,
                                       DWORD dwFlags)

{
       return cmsCreateProofingTransform(Input, InputFormat,
                                         Output, OutputFormat,
                                         NULL,
                                         Intent, INTENT_ABSOLUTE_COLORIMETRIC,
                                         dwFlags);
}


// Profiles are *NOT* closed

void LCMSEXPORT cmsDeleteTransform(cmsHTRANSFORM hTransform)
{
       _LPcmsTRANSFORM p = (_LPcmsTRANSFORM) (LPSTR) hTransform;

       if (p -> Device2PCS)
              cmsFreeLUT(p -> Device2PCS);
       if (p -> PCS2Device)
              cmsFreeLUT(p -> PCS2Device);
       if (p -> Gamut)
              cmsFreeLUT(p -> Gamut);
       if (p -> Preview)
              cmsFreeLUT(p -> Preview);
       if (p -> DeviceLink)
              cmsFreeLUT(p -> DeviceLink);
       if (p -> InMatShaper)
              cmsFreeMatShaper(p -> InMatShaper);
       if (p -> OutMatShaper)
              cmsFreeMatShaper(p -> OutMatShaper);
       if (p -> SmeltMatShaper)
              cmsFreeMatShaper(p -> SmeltMatShaper);
       if (p ->NamedColorList)
              cmsFreeNamedColorList(p ->NamedColorList);
       if (p -> GamutCheck)
            cmsFreeLUT(p -> GamutCheck); 

       LCMS_FREE_LOCK(&p->rwlock);

       _cmsFree((void *) p);
}


// Apply transform code
void LCMSEXPORT cmsDoTransform(cmsHTRANSFORM Transform,
                    LPVOID InputBuffer,
                    LPVOID OutputBuffer, unsigned int Size)

{

            _LPcmsTRANSFORM p = (_LPcmsTRANSFORM) (LPSTR) Transform;

            p -> StrideIn = p -> StrideOut = Size;
            
            p -> xform(p, InputBuffer, OutputBuffer, Size);

}


void LCMSEXPORT cmsSetAlarmCodes(int r, int g, int b)
{
       AlarmR = RGB_8_TO_16(r);
       AlarmG = RGB_8_TO_16(g);
       AlarmB = RGB_8_TO_16(b);
}

void LCMSEXPORT cmsGetAlarmCodes(int *r, int *g, int *b)
{
       *r = RGB_16_TO_8(AlarmR);
       *g = RGB_16_TO_8(AlarmG);
       *b = RGB_16_TO_8(AlarmB);
}

// Returns TRUE if the profile is implemented as matrix-shaper

LCMSBOOL LCMSEXPORT _cmsIsMatrixShaper(cmsHPROFILE hProfile)
{    
    switch (cmsGetColorSpace(hProfile)) {

    case icSigGrayData:
        
        return cmsIsTag(hProfile, icSigGrayTRCTag);

    case icSigRgbData:

        return (cmsIsTag(hProfile, icSigRedColorantTag) &&
                cmsIsTag(hProfile, icSigGreenColorantTag) &&
                cmsIsTag(hProfile, icSigBlueColorantTag) &&
                cmsIsTag(hProfile, icSigRedTRCTag) &&
                cmsIsTag(hProfile, icSigGreenTRCTag) &&
                cmsIsTag(hProfile, icSigBlueTRCTag));

    default:

        return FALSE;
    }
}


LCMSBOOL LCMSEXPORT cmsIsIntentSupported(cmsHPROFILE hProfile,
                                                int Intent, int UsedDirection)
{

     icTagSignature* TagTable;

     // Device link profiles only implements the intent in header

     if (cmsGetDeviceClass(hProfile) != icSigLinkClass) {

       switch (UsedDirection) {

       case LCMS_USED_AS_INPUT: TagTable = Device2PCS; break;
       case LCMS_USED_AS_OUTPUT:TagTable = PCS2Device; break;
       case LCMS_USED_AS_PROOF: TagTable = Preview; break;

       default:
        cmsSignalError(LCMS_ERRC_ABORTED, "Unexpected direction (%d)", UsedDirection);
        return FALSE;
       }

       if (cmsIsTag(hProfile, TagTable[Intent])) return TRUE;
       return _cmsIsMatrixShaper(hProfile);
     }

     return (cmsTakeRenderingIntent(hProfile) == Intent);
}

// Multiple profile transform. 
static
int MultiprofileSampler(register WORD In[], register WORD Out[], register LPVOID Cargo)
{
    cmsHTRANSFORM* Transforms = (cmsHTRANSFORM*) Cargo;
    int i;
    
    cmsDoTransform(Transforms[0], In, Out, 1);

    for (i=1; Transforms[i]; i++)
        cmsDoTransform(Transforms[i], Out, Out, 1);


    
    return TRUE;
}


static
int IsAllowedInSingleXform(icProfileClassSignature aClass)
{
    return (aClass == icSigInputClass) ||
           (aClass == icSigDisplayClass) ||
           (aClass == icSigOutputClass) ||
           (aClass == icSigColorSpaceClass);
}


// A multiprofile transform does chain several profiles into a single
// devicelink. It couls also be used to merge named color profiles into
// a single database.


cmsHTRANSFORM LCMSEXPORT cmsCreateMultiprofileTransform(cmsHPROFILE hProfiles[],
                                                                int nProfiles,
                                                                DWORD dwInput,
                                                                DWORD dwOutput,
                                                                int Intent,
                                                                DWORD dwFlags)
{
    cmsHTRANSFORM Transforms[257];
    DWORD dwPrecalcFlags = (dwFlags|cmsFLAGS_NOTPRECALC|cmsFLAGS_NOTCACHE);
    DWORD FormatInput, FormatOutput;
    cmsHPROFILE hLab, hXYZ, hProfile;   
    icColorSpaceSignature ColorSpace, CurrentColorSpace;
    icColorSpaceSignature ColorSpaceIn, ColorSpaceOut;   
    LPLUT Grid;
    int nGridPoints, ChannelsInput, ChannelsOutput = 3, i;    
    _LPcmsTRANSFORM p;        
    int nNamedColor;
    
    if (nProfiles > 255) {
        cmsSignalError(LCMS_ERRC_ABORTED, "What are you trying to do with more that 255 profiles?!?, of course aborted");
        return NULL;
    }

    // There is a simple case with just two profiles, try to catch it in order of getting
    // black preservation to work on this function, at least with two profiles.

    
    if (nProfiles == 2) {

        icProfileClassSignature Class1 = cmsGetDeviceClass(hProfiles[0]);
        icProfileClassSignature Class2 = cmsGetDeviceClass(hProfiles[1]);

        // Only input, output and display are allowed

        if (IsAllowedInSingleXform(Class1) && 
            IsAllowedInSingleXform(Class2)) 
                   return cmsCreateTransform(hProfiles[0], dwInput, hProfiles[1], dwOutput, Intent, dwFlags);
    }
    

    // Creates a phantom transform for latter filling
    p = (_LPcmsTRANSFORM) cmsCreateTransform(NULL, dwInput, 
                                             NULL, dwOutput, Intent, cmsFLAGS_NULLTRANSFORM);

    // If user wants null one, give it
    if (dwFlags & cmsFLAGS_NULLTRANSFORM) return (cmsHPROFILE) p;

    // Is a bunch of named color profiles? 
    nNamedColor = 0;
    for (i=0; i < nProfiles; i++) {
        if (cmsGetDeviceClass(hProfiles[i]) == icSigNamedColorClass)
                nNamedColor++;
    }


    if (nNamedColor == nProfiles) {

            // Yes, only named color. Create a named color-device
            // and append to named color table

            cmsDeleteTransform((cmsHTRANSFORM) p);

            p = (_LPcmsTRANSFORM) cmsCreateTransform(hProfiles[0], dwInput, NULL, dwOutput, Intent, dwFlags);
            for (i=1; i < nProfiles; i++) {
                    cmsReadICCnamedColorList(p, hProfiles[i], icSigNamedColor2Tag);
            }

            return p;   // Ok, done so far
    }
    else
    if (nNamedColor > 0) {

        cmsDeleteTransform((cmsHTRANSFORM) p);
        cmsSignalError(LCMS_ERRC_ABORTED, "Could not mix named color profiles with other types in multiprofile transform");
        return NULL;
    }


    // We will need a 3DCLUT for device link
    Grid =  cmsAllocLUT();
    if (!Grid) return NULL;

    // This one is our PCS (Always Lab)
    hLab  = cmsCreateLabProfile(NULL);
    hXYZ  = cmsCreateXYZProfile();

    if (!hLab || !hXYZ) goto ErrorCleanup;

    // Take some info....

    p ->EntryColorSpace = CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);    
    

    for (i=0; i < nProfiles; i++) {

        int lIsDeviceLink, lIsInput;

        // Check colorspace

        hProfile      = hProfiles[i];
        lIsDeviceLink = (cmsGetDeviceClass(hProfile) == icSigLinkClass);
        lIsInput      = (CurrentColorSpace != icSigXYZData) &&
                        (CurrentColorSpace != icSigLabData);

        if (lIsInput) {

            ColorSpaceIn    = cmsGetColorSpace(hProfile);
            ColorSpaceOut   = cmsGetPCS(hProfile);

        }
        else {
        
            ColorSpaceIn    = cmsGetPCS(hProfile);
            ColorSpaceOut   = cmsGetColorSpace(hProfile);
        }

        ChannelsInput  = _cmsChannelsOf(ColorSpaceIn);
        ChannelsOutput = _cmsChannelsOf(ColorSpaceOut);
               
        FormatInput    = BYTES_SH(2)|CHANNELS_SH(ChannelsInput);
        FormatOutput   = BYTES_SH(2)|CHANNELS_SH(ChannelsOutput);
        
        ColorSpace = ColorSpaceIn;
        

        if (ColorSpace == CurrentColorSpace) {
                                
            if (lIsDeviceLink) {

                Transforms[i]  = cmsCreateTransform(hProfile, FormatInput, 
                                                    NULL,     FormatOutput, 
                                                    Intent,   dwPrecalcFlags);                         
            }

            else {

                if (lIsInput) {

                        Transforms[i]  = cmsCreateTransform(hProfile, FormatInput, 
                                                  (ColorSpaceOut == icSigLabData ? hLab : hXYZ), FormatOutput, 
                                                  Intent, dwPrecalcFlags);           
                }
                else {
                        Transforms[i]  = cmsCreateTransform((ColorSpaceIn == icSigLabData ? hLab : hXYZ), FormatInput, 
                                                  hProfile, FormatOutput, 
                                                  Intent, dwPrecalcFlags);           
                    
                }
            } 
            
            
        }
        else  // Can come from pcs?
        if (CurrentColorSpace == icSigXYZData) {

            Transforms[i] = cmsCreateTransform(hXYZ, FormatInput, 
                                              hProfile, FormatOutput, 
                                              Intent, dwPrecalcFlags);            
            
        }
        else
        if (CurrentColorSpace == icSigLabData) {

            Transforms[i] = cmsCreateTransform(hLab, FormatInput, 
                                              hProfile, FormatOutput, 
                                              Intent, dwPrecalcFlags);
            
        } 
        else {
                cmsSignalError(LCMS_ERRC_ABORTED, "cmsCreateMultiprofileTransform: ColorSpace mismatch");
                goto ErrorCleanup;
        }

        CurrentColorSpace = ColorSpaceOut;
        
    }

    p ->ExitColorSpace = CurrentColorSpace;
    Transforms[i] = NULL;   // End marker 

    p ->InputProfile  = hProfiles[0];
    p ->OutputProfile = hProfiles[nProfiles - 1];

    nGridPoints = _cmsReasonableGridpointsByColorspace(p ->EntryColorSpace, dwFlags);
   
    ChannelsInput  = _cmsChannelsOf(cmsGetColorSpace(p ->InputProfile));
            
    Grid = cmsAlloc3DGrid(Grid, nGridPoints, ChannelsInput, ChannelsOutput);

    if (!(dwFlags & cmsFLAGS_NOPRELINEARIZATION))
           _cmsComputePrelinearizationTablesFromXFORM(Transforms, nProfiles, Grid);
      
    // Compute device link on 16-bit basis                
    if (!cmsSample3DGrid(Grid, MultiprofileSampler, (LPVOID) Transforms, Grid -> wFlags)) {

                cmsFreeLUT(Grid);
                goto ErrorCleanup;
    }

    // All ok, store the newly created LUT   
    p -> DeviceLink   = Grid;

    SetPrecalculatedTransform(p);
    
    for (i=nProfiles-1; i >= 0; --i)
        cmsDeleteTransform(Transforms[i]);


    if (hLab) cmsCloseProfile(hLab);
    if (hXYZ) cmsCloseProfile(hXYZ);

    
    if (p ->EntryColorSpace == icSigRgbData ||
        p ->EntryColorSpace == icSigCmyData) {
                   
                    p->DeviceLink -> CLut16params.Interp3D = cmsTetrahedralInterp16;
    }
    

    if ((Intent != INTENT_ABSOLUTE_COLORIMETRIC) && 
        !(dwFlags & cmsFLAGS_NOWHITEONWHITEFIXUP))
                            _cmsFixWhiteMisalignment(p);

    return (cmsHTRANSFORM) p;


ErrorCleanup:

    if (hLab) cmsCloseProfile(hLab);
    if (hXYZ) cmsCloseProfile(hXYZ);
    return NULL;
}


                       
double LCMSEXPORT cmsSetAdaptationState(double d)
{
    double OldVal = GlobalAdaptationState;

    if (d >= 0) 
            GlobalAdaptationState = d;

    return OldVal;
    
}
