//
//  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"


// Virtual (built-in) profiles
// -----------------------------------------------------------------------------------


// This function creates a profile based on White point, primaries and
// transfer functions.


cmsHPROFILE LCMSEXPORT cmsCreateRGBProfile(LPcmsCIExyY WhitePoint,
                                LPcmsCIExyYTRIPLE Primaries,
                                LPGAMMATABLE TransferFunction[3])
{
       cmsHPROFILE hICC;
       cmsCIEXYZ tmp;
       MAT3 MColorants;
       cmsCIEXYZTRIPLE Colorants;
       cmsCIExyY MaxWhite;
      

       hICC = _cmsCreateProfilePlaceholder();
       if (!hICC)                          // can't allocate
            return NULL;
              

       cmsSetDeviceClass(hICC,      icSigDisplayClass);
       cmsSetColorSpace(hICC,       icSigRgbData);
       cmsSetPCS(hICC,              icSigXYZData);
       cmsSetRenderingIntent(hICC,  INTENT_PERCEPTUAL); 
        
      
       // Implement profile using following tags:
       //
       //  1 icSigProfileDescriptionTag
       //  2 icSigMediaWhitePointTag
       //  3 icSigRedColorantTag
       //  4 icSigGreenColorantTag
       //  5 icSigBlueColorantTag
       //  6 icSigRedTRCTag
       //  7 icSigGreenTRCTag
       //  8 icSigBlueTRCTag

       // This conforms a standard RGB DisplayProfile as says ICC, and then I add

       // 9 icSigChromaticityTag

       // As addendum II
      

       // Fill-in the tags

       cmsAddTag(hICC, icSigDeviceMfgDescTag,       (LPVOID) "(lcms internal)");
       cmsAddTag(hICC, icSigProfileDescriptionTag,  (LPVOID) "lcms RGB virtual profile");
       cmsAddTag(hICC, icSigDeviceModelDescTag,     (LPVOID) "rgb built-in");      
          

       if (WhitePoint) {

       cmsxyY2XYZ(&tmp, WhitePoint);
       cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) &tmp);
       }

       if (WhitePoint && Primaries) {

        MaxWhite.x =  WhitePoint -> x;
        MaxWhite.y =  WhitePoint -> y;
        MaxWhite.Y =  1.0;

       if (!cmsBuildRGB2XYZtransferMatrix(&MColorants, &MaxWhite, Primaries))
       {
              cmsCloseProfile(hICC);
              return NULL;
       }  
     
       cmsAdaptMatrixToD50(&MColorants, &MaxWhite);

       Colorants.Red.X = MColorants.v[0].n[0];
       Colorants.Red.Y = MColorants.v[1].n[0];
       Colorants.Red.Z = MColorants.v[2].n[0];

       Colorants.Green.X = MColorants.v[0].n[1];
       Colorants.Green.Y = MColorants.v[1].n[1];
       Colorants.Green.Z = MColorants.v[2].n[1];

       Colorants.Blue.X = MColorants.v[0].n[2];
       Colorants.Blue.Y = MColorants.v[1].n[2];
       Colorants.Blue.Z = MColorants.v[2].n[2];

       cmsAddTag(hICC, icSigRedColorantTag,   (LPVOID) &Colorants.Red);
       cmsAddTag(hICC, icSigBlueColorantTag,  (LPVOID) &Colorants.Blue);
       cmsAddTag(hICC, icSigGreenColorantTag, (LPVOID) &Colorants.Green);
       }


       if (TransferFunction) {

       // In case of gamma, we must dup' the table pointer

        cmsAddTag(hICC, icSigRedTRCTag,   (LPVOID) TransferFunction[0]);
        cmsAddTag(hICC, icSigGreenTRCTag, (LPVOID) TransferFunction[1]);
        cmsAddTag(hICC, icSigBlueTRCTag,  (LPVOID) TransferFunction[2]);
       }

       if (Primaries) {
            cmsAddTag(hICC, icSigChromaticityTag, (LPVOID) Primaries);
       }

       return hICC;
}



// This function creates a profile based on White point and transfer function.

cmsHPROFILE   LCMSEXPORT cmsCreateGrayProfile(LPcmsCIExyY WhitePoint,
                                              LPGAMMATABLE TransferFunction)
{
       cmsHPROFILE hICC;
       cmsCIEXYZ tmp;              
      
       
       hICC = _cmsCreateProfilePlaceholder();
       if (!hICC)                          // can't allocate
            return NULL;
              

       cmsSetDeviceClass(hICC,      icSigDisplayClass);
       cmsSetColorSpace(hICC,       icSigGrayData);
       cmsSetPCS(hICC,              icSigXYZData);
       cmsSetRenderingIntent(hICC,  INTENT_PERCEPTUAL); 



       // Implement profile using following tags:
       //
       //  1 icSigProfileDescriptionTag
       //  2 icSigMediaWhitePointTag
       //  6 icSigGrayTRCTag
       
       // This conforms a standard Gray DisplayProfile 
            
       // Fill-in the tags

       
       cmsAddTag(hICC, icSigDeviceMfgDescTag,       (LPVOID) "(lcms internal)");
       cmsAddTag(hICC, icSigProfileDescriptionTag,  (LPVOID) "lcms gray virtual profile");
       cmsAddTag(hICC, icSigDeviceModelDescTag,     (LPVOID) "gray built-in");      
    

       if (WhitePoint) {

       cmsxyY2XYZ(&tmp, WhitePoint);
       cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) &tmp);
       }


       if (TransferFunction) {

       // In case of gamma, we must dup' the table pointer

       cmsAddTag(hICC, icSigGrayTRCTag, (LPVOID) TransferFunction);
       }

       return hICC;

}


static
int IsPCS(icColorSpaceSignature ColorSpace)
{
    return (ColorSpace == icSigXYZData ||
            ColorSpace == icSigLabData);
}

static
void FixColorSpaces(cmsHPROFILE hProfile, 
                              icColorSpaceSignature ColorSpace, 
                              icColorSpaceSignature PCS,
                              DWORD dwFlags)
{

    if (dwFlags & cmsFLAGS_GUESSDEVICECLASS) {

            if (IsPCS(ColorSpace) && IsPCS(PCS)) { 

                    cmsSetDeviceClass(hProfile,      icSigAbstractClass);
                    cmsSetColorSpace(hProfile,       ColorSpace);
                    cmsSetPCS(hProfile,              PCS);
                    return;
            }

            if (IsPCS(ColorSpace) && !IsPCS(PCS)) {

                    cmsSetDeviceClass(hProfile, icSigOutputClass);
                    cmsSetPCS(hProfile,         ColorSpace);
                    cmsSetColorSpace(hProfile,  PCS);        
                    return;
            }

            if (IsPCS(PCS) && !IsPCS(ColorSpace)) {

                   cmsSetDeviceClass(hProfile,  icSigInputClass);
                   cmsSetColorSpace(hProfile,   ColorSpace);
                   cmsSetPCS(hProfile,          PCS);
                   return;
            }
    }

    cmsSetDeviceClass(hProfile,      icSigLinkClass);
    cmsSetColorSpace(hProfile,       ColorSpace);
    cmsSetPCS(hProfile,              PCS);

}


static
cmsHPROFILE CreateNamedColorDevicelink(cmsHTRANSFORM xform)
{
    _LPcmsTRANSFORM v = (_LPcmsTRANSFORM) xform;
    cmsHPROFILE hICC;
    cmsCIEXYZ WhitePoint;    
    int i, nColors;
    size_t Size;
    LPcmsNAMEDCOLORLIST nc2;


    hICC = _cmsCreateProfilePlaceholder();
    if (hICC == NULL) return NULL;

    cmsSetRenderingIntent(hICC, v -> Intent); 
    cmsSetDeviceClass(hICC, icSigNamedColorClass);
    cmsSetColorSpace(hICC, v ->ExitColorSpace);
    cmsSetPCS(hICC, cmsGetPCS(v ->InputProfile));
    cmsTakeMediaWhitePoint(&WhitePoint, v ->InputProfile);

    cmsAddTag(hICC, icSigMediaWhitePointTag,  &WhitePoint);
    cmsAddTag(hICC, icSigDeviceMfgDescTag,       (LPVOID) "LittleCMS");       
    cmsAddTag(hICC, icSigProfileDescriptionTag,  (LPVOID) "Named color Device link");
    cmsAddTag(hICC, icSigDeviceModelDescTag,     (LPVOID) "Named color Device link");      
    

    nColors = cmsNamedColorCount(xform);
    nc2     = cmsAllocNamedColorList(nColors);

    Size = sizeof(cmsNAMEDCOLORLIST) + (sizeof(cmsNAMEDCOLOR) * (nColors-1));

    CopyMemory(nc2, v->NamedColorList, Size);
    nc2 ->ColorantCount = _cmsChannelsOf(v ->ExitColorSpace);

    for (i=0; i < nColors; i++) {
        cmsDoTransform(xform, &i, nc2 ->List[i].DeviceColorant, 1);
    }

    cmsAddTag(hICC, icSigNamedColor2Tag, (void*) nc2);
    cmsFreeNamedColorList(nc2);

    return hICC;
}


// Does convert a transform into a device link profile

cmsHPROFILE LCMSEXPORT cmsTransform2DeviceLink(cmsHTRANSFORM hTransform, DWORD dwFlags)
{
    cmsHPROFILE hICC;
    _LPcmsTRANSFORM v = (_LPcmsTRANSFORM) hTransform;
    LPLUT Lut;
    LCMSBOOL MustFreeLUT;
    LPcmsNAMEDCOLORLIST InputColorant = NULL;
    LPcmsNAMEDCOLORLIST OutputColorant = NULL;
    
    
    // Check if is a named color transform
    
    if (cmsGetDeviceClass(v ->InputProfile) == icSigNamedColorClass) {
        
        return CreateNamedColorDevicelink(hTransform);
        
    }
    
    if (v ->DeviceLink) {
        
        Lut = v -> DeviceLink;
        MustFreeLUT = FALSE;
    }
    else {
        
        Lut = _cmsPrecalculateDeviceLink(hTransform, dwFlags);
        if (!Lut) return NULL;
        MustFreeLUT = TRUE;
    }
    
    hICC = _cmsCreateProfilePlaceholder();
    if (!hICC) {                          // can't allocate
        
        if (MustFreeLUT) cmsFreeLUT(Lut);
        return NULL;
    }
    
    
    FixColorSpaces(hICC, v -> EntryColorSpace, v -> ExitColorSpace, dwFlags);             
    
    cmsSetRenderingIntent(hICC,  v -> Intent); 
    
    // Implement devicelink profile using following tags:
    //
    //  1 icSigProfileDescriptionTag
    //  2 icSigMediaWhitePointTag
    //  3 icSigAToB0Tag
    
    
    cmsAddTag(hICC, icSigDeviceMfgDescTag,       (LPVOID) "LittleCMS");       
    cmsAddTag(hICC, icSigProfileDescriptionTag,  (LPVOID) "Device link");
    cmsAddTag(hICC, icSigDeviceModelDescTag,     (LPVOID) "Device link");      
    
    
    cmsAddTag(hICC, icSigMediaWhitePointTag,  (LPVOID) cmsD50_XYZ());
    
    if (cmsGetDeviceClass(hICC) == icSigOutputClass) {
                
        cmsAddTag(hICC, icSigBToA0Tag, (LPVOID) Lut);
    }
    else
        cmsAddTag(hICC, icSigAToB0Tag, (LPVOID) Lut);
    
    
    
    // Try to read input and output colorant table
    if (cmsIsTag(v ->InputProfile, icSigColorantTableTag)) {
        
        // Input table can only come in this way.
        InputColorant = cmsReadColorantTable(v ->InputProfile, icSigColorantTableTag);
    }
    
    // Output is a little bit more complex.    
    if (cmsGetDeviceClass(v ->OutputProfile) == icSigLinkClass) {
        
        // This tag may exist only on devicelink profiles.        
        if (cmsIsTag(v ->OutputProfile, icSigColorantTableOutTag)) {
            
            OutputColorant = cmsReadColorantTable(v ->OutputProfile, icSigColorantTableOutTag);
        }
        
    } else {
        
        if (cmsIsTag(v ->OutputProfile, icSigColorantTableTag)) {
            
            OutputColorant = cmsReadColorantTable(v ->OutputProfile, icSigColorantTableTag);
        }     
    }
    
    if (InputColorant) 
           cmsAddTag(hICC, icSigColorantTableTag, InputColorant);       
       
    if (OutputColorant) 
           cmsAddTag(hICC, icSigColorantTableOutTag, OutputColorant);       
       
       
       
    if (MustFreeLUT) cmsFreeLUT(Lut);
    if (InputColorant) cmsFreeNamedColorList(InputColorant);
    if (OutputColorant) cmsFreeNamedColorList(OutputColorant);
       
    return hICC;
       
}


// This is a devicelink operating in the target colorspace with as many transfer
// functions as components

cmsHPROFILE LCMSEXPORT cmsCreateLinearizationDeviceLink(icColorSpaceSignature ColorSpace,
                                                        LPGAMMATABLE TransferFunctions[])
{
       cmsHPROFILE hICC;
       LPLUT Lut;
           
        
       hICC = _cmsCreateProfilePlaceholder();
       if (!hICC)                          // can't allocate
            return NULL;


       cmsSetDeviceClass(hICC,      icSigLinkClass);
       cmsSetColorSpace(hICC,       ColorSpace);
       cmsSetPCS(hICC,              ColorSpace);
       cmsSetRenderingIntent(hICC,  INTENT_PERCEPTUAL); 

     
       // Creates a LUT with prelinearization step only
       Lut = cmsAllocLUT();
       if (Lut == NULL) return NULL;

       // Set up channels
       Lut ->InputChan = Lut ->OutputChan = _cmsChannelsOf(ColorSpace);

       // Copy tables to LUT
       cmsAllocLinearTable(Lut, TransferFunctions, 1);

       // Create tags       
       cmsAddTag(hICC, icSigDeviceMfgDescTag,       (LPVOID) "(lcms internal)");
       cmsAddTag(hICC, icSigProfileDescriptionTag,  (LPVOID) "lcms linearization device link");
       cmsAddTag(hICC, icSigDeviceModelDescTag,     (LPVOID) "linearization built-in");      
    
       cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) cmsD50_XYZ());
       cmsAddTag(hICC, icSigAToB0Tag, (LPVOID) Lut);
       
       // LUT is already on virtual profile
       cmsFreeLUT(Lut);

       // Ok, done
       return hICC;
}


// Ink-limiting algorithm
//
//  Sum = C + M + Y + K 
//  If Sum > InkLimit 
//        Ratio= 1 - (Sum - InkLimit) / (C + M + Y)
//        if Ratio <0 
//              Ratio=0
//        endif     
//     Else 
//         Ratio=1
//     endif
//
//     C = Ratio * C
//     M = Ratio * M
//     Y = Ratio * Y
//     K: Does not change
   
static
int InkLimitingSampler(register WORD In[], register WORD Out[], register LPVOID Cargo)
{
        double InkLimit = *(double *) Cargo;
        double SumCMY, SumCMYK, Ratio;
    
        InkLimit = (InkLimit * 655.35);

        SumCMY   = In[0]  + In[1] + In[2];
        SumCMYK  = SumCMY + In[3];      

        if (SumCMYK > InkLimit) {

                Ratio = 1 - ((SumCMYK - InkLimit) / SumCMY);
                if (Ratio < 0)
                        Ratio = 0;
        }
        else Ratio = 1;
                
        Out[0] = (WORD) floor(In[0] * Ratio + 0.5);     // C
        Out[1] = (WORD) floor(In[1] * Ratio + 0.5);     // M
        Out[2] = (WORD) floor(In[2] * Ratio + 0.5);     // Y

        Out[3] = In[3];                                 // K (untouched)

        return TRUE;
}

// This is a devicelink operating in CMYK for ink-limiting

cmsHPROFILE LCMSEXPORT cmsCreateInkLimitingDeviceLink(icColorSpaceSignature ColorSpace,
                                                        double Limit)
{
       cmsHPROFILE hICC;
       LPLUT Lut;
           
       if (ColorSpace != icSigCmykData) {
            cmsSignalError(LCMS_ERRC_ABORTED, "InkLimiting: Only CMYK currently supported");
            return NULL;
       }

       if (Limit < 0.0 || Limit > 400) {

           cmsSignalError(LCMS_ERRC_WARNING, "InkLimiting: Limit should be between 0..400");        
           if (Limit < 0) Limit = 0;
           if (Limit > 400) Limit = 400;
       
       }

      hICC = _cmsCreateProfilePlaceholder();
       if (!hICC)                          // can't allocate
            return NULL;
              

       cmsSetDeviceClass(hICC,      icSigLinkClass);
       cmsSetColorSpace(hICC,       ColorSpace);
       cmsSetPCS(hICC,              ColorSpace);
       cmsSetRenderingIntent(hICC,  INTENT_PERCEPTUAL); 

      
       // Creates a LUT with 3D grid only
       Lut = cmsAllocLUT();
       if (Lut == NULL) {
           cmsCloseProfile(hICC);
           return NULL;
           }


       cmsAlloc3DGrid(Lut, 17, _cmsChannelsOf(ColorSpace), 
                               _cmsChannelsOf(ColorSpace));

       if (!cmsSample3DGrid(Lut, InkLimitingSampler, (LPVOID) &Limit, 0)) {

                // Shouldn't reach here
                cmsFreeLUT(Lut);
                cmsCloseProfile(hICC);
                return NULL;
       }    
       
       // Create tags
        
       cmsAddTag(hICC, icSigDeviceMfgDescTag,      (LPVOID) "(lcms internal)"); 
       cmsAddTag(hICC, icSigProfileDescriptionTag, (LPVOID) "lcms ink limiting device link");  
       cmsAddTag(hICC, icSigDeviceModelDescTag,    (LPVOID) "ink limiting built-in");      
       
       cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) cmsD50_XYZ());

       cmsAddTag(hICC, icSigAToB0Tag, (LPVOID) Lut);
       
       // LUT is already on virtual profile
       cmsFreeLUT(Lut);

       // Ok, done
       return hICC;
}



static
LPLUT Create3x3EmptyLUT(void)
{
        LPLUT AToB0 = cmsAllocLUT();
        if (AToB0 == NULL) return NULL;

        AToB0 -> InputChan = AToB0 -> OutputChan = 3;
        return AToB0;
}



// Creates a fake Lab identity.
cmsHPROFILE LCMSEXPORT cmsCreateLabProfile(LPcmsCIExyY WhitePoint)
{
        cmsHPROFILE hProfile;        
        LPLUT Lut;

        hProfile = cmsCreateRGBProfile(WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
        if (hProfile == NULL) return NULL;

        cmsSetDeviceClass(hProfile, icSigAbstractClass);
        cmsSetColorSpace(hProfile,  icSigLabData);
        cmsSetPCS(hProfile,         icSigLabData);

        cmsAddTag(hProfile, icSigDeviceMfgDescTag,     (LPVOID) "(lcms internal)"); 
        cmsAddTag(hProfile, icSigProfileDescriptionTag, (LPVOID) "lcms Lab identity");      
        cmsAddTag(hProfile, icSigDeviceModelDescTag,    (LPVOID) "Lab built-in");      


       // An empty LUTs is all we need
       Lut = Create3x3EmptyLUT();
       if (Lut == NULL) {
           cmsCloseProfile(hProfile);
           return NULL;
           }

       cmsAddTag(hProfile, icSigAToB0Tag,    (LPVOID) Lut);
       cmsAddTag(hProfile, icSigBToA0Tag,    (LPVOID) Lut);
    
       cmsFreeLUT(Lut);

       return hProfile;
}


// Creates a fake Lab identity.
cmsHPROFILE LCMSEXPORT cmsCreateLab4Profile(LPcmsCIExyY WhitePoint)
{
        cmsHPROFILE hProfile;        
        LPLUT Lut;

        hProfile = cmsCreateRGBProfile(WhitePoint == NULL ? cmsD50_xyY() : WhitePoint, NULL, NULL);
        if (hProfile == NULL) return NULL;

        cmsSetProfileICCversion(hProfile, 0x4000000);

        cmsSetDeviceClass(hProfile, icSigAbstractClass);
        cmsSetColorSpace(hProfile,  icSigLabData);
        cmsSetPCS(hProfile,         icSigLabData);

        cmsAddTag(hProfile, icSigDeviceMfgDescTag,     (LPVOID) "(lcms internal)"); 
        cmsAddTag(hProfile, icSigProfileDescriptionTag, (LPVOID) "lcms Lab identity v4");      
        cmsAddTag(hProfile, icSigDeviceModelDescTag,    (LPVOID) "Lab v4 built-in");      


       // An empty LUTs is all we need
       Lut = Create3x3EmptyLUT();
       if (Lut == NULL) {
           cmsCloseProfile(hProfile);
           return NULL;
           }

       Lut -> wFlags |= LUT_V4_INPUT_EMULATE_V2;
       cmsAddTag(hProfile, icSigAToB0Tag,    (LPVOID) Lut);

       Lut -> wFlags |= LUT_V4_OUTPUT_EMULATE_V2;
       cmsAddTag(hProfile, icSigBToA0Tag,    (LPVOID) Lut);
    
       cmsFreeLUT(Lut);

       return hProfile;
}



// Creates a fake XYZ identity
cmsHPROFILE LCMSEXPORT cmsCreateXYZProfile(void)
{
        cmsHPROFILE hProfile;       
        LPLUT Lut;

        hProfile = cmsCreateRGBProfile(cmsD50_xyY(), NULL, NULL);
        if (hProfile == NULL) return NULL;

        cmsSetDeviceClass(hProfile, icSigAbstractClass);
        cmsSetColorSpace(hProfile, icSigXYZData);
        cmsSetPCS(hProfile,  icSigXYZData);

        cmsAddTag(hProfile, icSigDeviceMfgDescTag,      (LPVOID) "(lcms internal)");    
        cmsAddTag(hProfile, icSigProfileDescriptionTag, (LPVOID) "lcms XYZ identity");     
        cmsAddTag(hProfile, icSigDeviceModelDescTag,    (LPVOID)  "XYZ built-in");

       // An empty LUTs is all we need
       Lut = Create3x3EmptyLUT();
       if (Lut == NULL) {
           cmsCloseProfile(hProfile);
           return NULL;
           }

       cmsAddTag(hProfile, icSigAToB0Tag,    (LPVOID) Lut);
       cmsAddTag(hProfile, icSigBToA0Tag,    (LPVOID) Lut);
       cmsAddTag(hProfile, icSigPreview0Tag, (LPVOID) Lut);

       cmsFreeLUT(Lut);    
       return hProfile;
}



/*

If  R’sRGB,G’sRGB, B’sRGB < 0.04045

    R =  R’sRGB / 12.92
    G =  G’sRGB / 12.92
    B =  B’sRGB / 12.92

 

else if  R’sRGB,G’sRGB, B’sRGB >= 0.04045

    R = ((R’sRGB + 0.055) / 1.055)^2.4
    G = ((G’sRGB + 0.055) / 1.055)^2.4
    B = ((B’sRGB + 0.055) / 1.055)^2.4

  */

static
LPGAMMATABLE Build_sRGBGamma(void)
{
    double Parameters[5];

    Parameters[0] = 2.4;
    Parameters[1] = 1. / 1.055;
    Parameters[2] = 0.055 / 1.055;
    Parameters[3] = 1. / 12.92;
    Parameters[4] = 0.04045;    // d

    return cmsBuildParametricGamma(1024, 4, Parameters);
}

// Create the ICC virtual profile for sRGB space 
cmsHPROFILE LCMSEXPORT cmsCreate_sRGBProfile(void)
{
       cmsCIExyY       D65;
       cmsCIExyYTRIPLE Rec709Primaries = {
                                   {0.6400, 0.3300, 1.0},
                                   {0.3000, 0.6000, 1.0},
                                   {0.1500, 0.0600, 1.0}
                                   };
       LPGAMMATABLE Gamma22[3];
       cmsHPROFILE  hsRGB;
 
       cmsWhitePointFromTemp(6504, &D65);
       Gamma22[0] = Gamma22[1] = Gamma22[2] = Build_sRGBGamma();
           
       hsRGB = cmsCreateRGBProfile(&D65, &Rec709Primaries, Gamma22);
       cmsFreeGamma(Gamma22[0]);
       if (hsRGB == NULL) return NULL;

      
       cmsAddTag(hsRGB, icSigDeviceMfgDescTag,      (LPVOID) "(lcms internal)");
       cmsAddTag(hsRGB, icSigDeviceModelDescTag,    (LPVOID) "sRGB built-in");
       cmsAddTag(hsRGB, icSigProfileDescriptionTag, (LPVOID) "sRGB built-in");
        
       return hsRGB;
}



typedef struct {
                double Brightness;
                double Contrast;
                double Hue;
                double Saturation;
                cmsCIEXYZ WPsrc, WPdest;

} BCHSWADJUSTS, *LPBCHSWADJUSTS;


static
int bchswSampler(register WORD In[], register WORD Out[], register LPVOID Cargo)
{
    cmsCIELab LabIn, LabOut;
    cmsCIELCh LChIn, LChOut;
    cmsCIEXYZ XYZ;
    LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo;
    

    cmsLabEncoded2Float(&LabIn, In);
    

    cmsLab2LCh(&LChIn, &LabIn);

    // Do some adjusts on LCh
    
    LChOut.L = LChIn.L * bchsw ->Contrast + bchsw ->Brightness;
    LChOut.C = LChIn.C + bchsw -> Saturation;
    LChOut.h = LChIn.h + bchsw -> Hue;
    
        
    cmsLCh2Lab(&LabOut, &LChOut);

    // Move white point in Lab

    cmsLab2XYZ(&bchsw ->WPsrc,  &XYZ, &LabOut);
    cmsXYZ2Lab(&bchsw ->WPdest, &LabOut, &XYZ);
    
    // Back to encoded

    cmsFloat2LabEncoded(Out, &LabOut);
    
    return TRUE;
}


// Creates an abstract profile operating in Lab space for Brightness,
// contrast, Saturation and white point displacement

cmsHPROFILE LCMSEXPORT cmsCreateBCHSWabstractProfile(int nLUTPoints,
                                                     double Bright, 
                                                     double Contrast,
                                                     double Hue,
                                                     double Saturation,
                                                     int TempSrc, 
                                                     int TempDest)
{
     cmsHPROFILE hICC;
     LPLUT Lut;
     BCHSWADJUSTS bchsw;
     cmsCIExyY WhitePnt;

     bchsw.Brightness = Bright;
     bchsw.Contrast   = Contrast;
     bchsw.Hue        = Hue;
     bchsw.Saturation = Saturation;
     
     cmsWhitePointFromTemp(TempSrc,  &WhitePnt);         
     cmsxyY2XYZ(&bchsw.WPsrc, &WhitePnt);

     cmsWhitePointFromTemp(TempDest, &WhitePnt);
     cmsxyY2XYZ(&bchsw.WPdest, &WhitePnt);
    
      hICC = _cmsCreateProfilePlaceholder();
       if (!hICC)                          // can't allocate
            return NULL;
              

       cmsSetDeviceClass(hICC,      icSigAbstractClass);
       cmsSetColorSpace(hICC,       icSigLabData);
       cmsSetPCS(hICC,              icSigLabData);

       cmsSetRenderingIntent(hICC,  INTENT_PERCEPTUAL); 

      
       // Creates a LUT with 3D grid only
       Lut = cmsAllocLUT();
       if (Lut == NULL) {
           cmsCloseProfile(hICC);
           return NULL;
           }

       cmsAlloc3DGrid(Lut, nLUTPoints, 3, 3);

       if (!cmsSample3DGrid(Lut, bchswSampler, (LPVOID) &bchsw, 0)) {

                // Shouldn't reach here
                cmsFreeLUT(Lut);
                cmsCloseProfile(hICC);
                return NULL;
       }    
       
       // Create tags
        
       cmsAddTag(hICC, icSigDeviceMfgDescTag,      (LPVOID) "(lcms internal)"); 
       cmsAddTag(hICC, icSigProfileDescriptionTag, (LPVOID) "lcms BCHSW abstract profile");  
       cmsAddTag(hICC, icSigDeviceModelDescTag,    (LPVOID) "BCHSW built-in");      
       
       cmsAddTag(hICC, icSigMediaWhitePointTag, (LPVOID) cmsD50_XYZ());

       cmsAddTag(hICC, icSigAToB0Tag, (LPVOID) Lut);
       
       // LUT is already on virtual profile
       cmsFreeLUT(Lut);

       // Ok, done
       return hICC;

}


// Creates a fake NULL profile. This profile return 1 channel as always 0. 
// Is useful only for gamut checking tricks

cmsHPROFILE LCMSEXPORT cmsCreateNULLProfile(void)
{
        cmsHPROFILE hProfile;        
        LPLUT Lut;
        LPGAMMATABLE EmptyTab;

        hProfile = _cmsCreateProfilePlaceholder();
        if (!hProfile)                          // can't allocate
                return NULL;
      
        cmsSetDeviceClass(hProfile, icSigOutputClass);
        cmsSetColorSpace(hProfile,  icSigGrayData);
        cmsSetPCS(hProfile,         icSigLabData);
        

       // An empty LUTs is all we need
       Lut = cmsAllocLUT();
       if (Lut == NULL) {
           cmsCloseProfile(hProfile);
           return NULL;
           }

       Lut -> InputChan = 3;
       Lut -> OutputChan = 1;

       EmptyTab = cmsAllocGamma(2);
       EmptyTab ->GammaTable[0] = 0;
       EmptyTab ->GammaTable[1] = 0;

       cmsAllocLinearTable(Lut, &EmptyTab, 2);
        
       cmsAddTag(hProfile, icSigBToA0Tag, (LPVOID) Lut);
    
       cmsFreeLUT(Lut);
       cmsFreeGamma(EmptyTab);
       
       return hProfile;
}
