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

// ICC profile serialization


#include "lcms.h"

// ----------------------------------------------------------------- Tag Serialization

// Alignment of ICC file format uses 4 bytes DWORD

#define ALIGNLONG(x) (((x)+3) & ~(3))         // Aligns to DWORD boundary


static int GlobalLanguageCode;   // Language & country descriptors, for ICC 4.0 support
static int GlobalCountryCode;

                           
#ifdef __BEOS__
#       define USE_CUSTOM_SWAB  1
#endif


#ifdef USE_CUSTOM_SWAB

// Replacement to swab function, thanks to YNOP
// for providing the BeOS port
//
// from: @(#)swab.c       5.10 (Berkeley)  3/6/91

static
void xswab(const void *from, void *to, size_t len)
{
         register unsigned long temp;
         register int n;
         register char *fp, *tp;

         n = (len >> 1) + 1;
         fp = (char *)from;
         tp = (char *)to;
#define STEP    temp = *fp++,*tp++ = *fp++,*tp++ = temp
         /* round to multiple of 8 */
         while ((--n) & 07)
                 STEP;
         n >>= 3;
         while (--n >= 0) {

                 STEP; STEP; STEP; STEP;
                 STEP; STEP; STEP; STEP;
         }
#undef STEP
}
#else
#define xswab swab
#endif


//
//      Little-Endian to Big-Endian
//

#ifdef USE_BIG_ENDIAN
#define AdjustEndianess16(a)
#define AdjustEndianess32(a)
#define AdjustEndianessArray16(a, b)
#else

static
void AdjustEndianess16(LPBYTE pByte)
{
       BYTE tmp;

       tmp = pByte[0];
       pByte[0] = pByte[1];
       pByte[1] = tmp;
}

static
void AdjustEndianess32(LPBYTE pByte)
{
        BYTE temp1;
        BYTE temp2;

        temp1 = *pByte++;
        temp2 = *pByte++;
        *(pByte-1) = *pByte;
        *pByte++ = temp2;
        *(pByte-3) = *pByte;
        *pByte = temp1;
}


// swap bytes in a array of words

static
void AdjustEndianessArray16(LPWORD p, size_t num_words)
{
       xswab((char*) p, (char*)p, (int) num_words * sizeof(WORD));
}

#endif


// Transports to properly encoded values - note that icc profiles does use
// big endian notation.

static
icInt32Number TransportValue32(icInt32Number Value)
{
       icInt32Number Temp = Value;

       AdjustEndianess32((LPBYTE) &Temp);
       return Temp;
}

static
WORD TransportValue16(WORD Value)
{
       WORD Temp = Value;

       AdjustEndianess16((LPBYTE) &Temp);
       return Temp;
}


// from Fixed point 8.8 to double

static
double Convert8Fixed8(WORD fixed8)
{
       BYTE msb, lsb;

       lsb = (BYTE) (fixed8 & 0xff);
       msb = (BYTE) (((WORD) fixed8 >> 8) & 0xff);

       return (double) ((double) msb + ((double) lsb / 256.0));
}


// from Fixed point 15.16 to double
static
double Convert15Fixed16(icS15Fixed16Number fix32)
{
    double floater, sign, mid, hack;
    int Whole, FracPart;


    AdjustEndianess32((LPBYTE) &fix32);

    sign  = (fix32 < 0 ? -1 : 1);
    fix32 = abs(fix32);

    Whole = LOWORD(fix32 >> 16);
    FracPart  = LOWORD(fix32 & 0x0000ffffL);

    hack    = 65536.0;
    mid     = (double) FracPart / hack;
    floater = (double) Whole + mid;

    return sign * floater;
}


// Auxiliar-- read base and return type

static
icTagTypeSignature ReadBase(LPLCMSICCPROFILE Icc)
{
    icTagBase Base;

    Icc -> Read(&Base, sizeof(icTagBase), 1, Icc);
    AdjustEndianess32((LPBYTE) &Base.sig);

    return Base.sig;
}


static
void DecodeDateTimeNumber(const icDateTimeNumber *Source, struct tm *Dest)
{
    Dest->tm_sec   = TransportValue16(Source->seconds);
    Dest->tm_min   = TransportValue16(Source->minutes);
    Dest->tm_hour  = TransportValue16(Source->hours);
    Dest->tm_mday  = TransportValue16(Source->day);
    Dest->tm_mon   = TransportValue16(Source->month) - 1;
    Dest->tm_year  = TransportValue16(Source->year) - 1900;
    Dest->tm_wday  = -1;
    Dest->tm_yday  = -1;
    Dest->tm_isdst = 0;
}

static
void EncodeDateTimeNumber(icDateTimeNumber *Dest, const struct tm *Source)
{
    Dest->seconds = TransportValue16((WORD) Source->tm_sec);
    Dest->minutes = TransportValue16((WORD) Source->tm_min);
    Dest->hours   = TransportValue16((WORD) Source->tm_hour);
    Dest->day     = TransportValue16((WORD) Source->tm_mday);
    Dest->month   = TransportValue16((WORD) (Source->tm_mon + 1));
    Dest->year    = TransportValue16((WORD) (Source->tm_year + 1900));
}


// Jun-21-2000: Some profiles (those that comes with W2K) comes
// with the media white (media black?) x 100. Add a sanity check

static
void NormalizeXYZ(LPcmsCIEXYZ Dest)
{
    while (Dest -> X > 2. &&
           Dest -> Y > 2. &&
           Dest -> Z > 2.) {

               Dest -> X /= 10.;
               Dest -> Y /= 10.;
               Dest -> Z /= 10.;
       }
}

// Evaluates a XYZ tristimulous across chromatic adaptation matrix

static
void EvalCHRM(LPcmsCIEXYZ Dest, LPMAT3 Chrm, LPcmsCIEXYZ Src)
{
    VEC3 d, s;

    s.n[VX] = Src -> X;
    s.n[VY] = Src -> Y;
    s.n[VZ] = Src -> Z;

    MAT3eval(&d, Chrm, &s);

    Dest ->X = d.n[VX];
    Dest ->Y = d.n[VY];
    Dest ->Z = d.n[VZ];

}


// Read profile header and validate it

static
LPLCMSICCPROFILE ReadHeader(LPLCMSICCPROFILE Icc, LCMSBOOL lIsFromMemory)
{
     icTag Tag;
     icHeader Header;
     icInt32Number TagCount, i;
    
       if (Icc -> Read(&Header, sizeof(icHeader), 1, Icc) != 1) 
                      goto ErrorCleanup;

       // Convert endian

       AdjustEndianess32((LPBYTE) &Header.size);
       AdjustEndianess32((LPBYTE) &Header.cmmId);
       AdjustEndianess32((LPBYTE) &Header.version);
       AdjustEndianess32((LPBYTE) &Header.deviceClass);
       AdjustEndianess32((LPBYTE) &Header.colorSpace);
       AdjustEndianess32((LPBYTE) &Header.pcs);
       AdjustEndianess32((LPBYTE) &Header.magic);
       AdjustEndianess32((LPBYTE) &Header.flags);
       AdjustEndianess32((LPBYTE) &Header.attributes[0]);
       AdjustEndianess32((LPBYTE) &Header.renderingIntent);

       // Validate it

       if (Header.magic != icMagicNumber) goto ErrorCleanup;
                   
       if (Icc ->Read(&TagCount, sizeof(icInt32Number), 1, Icc) != 1)
                     goto ErrorCleanup;

       AdjustEndianess32((LPBYTE) &TagCount);

       Icc -> DeviceClass     = Header.deviceClass;
       Icc -> ColorSpace      = Header.colorSpace;
       Icc -> PCS             = Header.pcs;
       Icc -> RenderingIntent = (icRenderingIntent) Header.renderingIntent;
       Icc -> flags           = Header.flags;
       Icc -> attributes      = Header.attributes[0];
       Icc -> Illuminant.X    = Convert15Fixed16(Header.illuminant.X);
       Icc -> Illuminant.Y    = Convert15Fixed16(Header.illuminant.Y);
       Icc -> Illuminant.Z    = Convert15Fixed16(Header.illuminant.Z);
       Icc -> Version         = Header.version;

       // Get creation date/time

       DecodeDateTimeNumber(&Header.date, &Icc ->Created);

       // Fix illuminant, some profiles are broken in this field!
       
       Icc ->Illuminant = *cmsD50_XYZ();

       // The profile ID are 16 raw bytes

       CopyMemory(Icc ->ProfileID, Header.reserved, 16);

       // Get rid of possible wrong profiles

       NormalizeXYZ(&Icc  -> Illuminant);

       // Read tag directory

       if (TagCount > MAX_TABLE_TAG) {

           cmsSignalError(LCMS_ERRC_ABORTED, "Too many tags (%d)", TagCount);
           goto ErrorCleanup;
       }

       Icc -> TagCount = TagCount;
       for (i=0; i < TagCount; i++) {

              if (Icc ->Read(&Tag, sizeof(icTag), 1, Icc) != 1) 
                  goto ErrorCleanup;

              AdjustEndianess32((LPBYTE) &Tag.offset);
              AdjustEndianess32((LPBYTE) &Tag.size);
              AdjustEndianess32((LPBYTE) &Tag.sig);            // Signature

              // Perform some sanity check. Offset + size should fall inside file.

              if (Tag.offset + Tag.size > Header.size) goto ErrorCleanup;

              Icc -> TagNames[i]   = Tag.sig;
              Icc -> TagOffsets[i] = Tag.offset;
              Icc -> TagSizes[i]   = Tag.size;
       }
          
       return Icc;


ErrorCleanup:

       Icc ->Close(Icc);

       if (lIsFromMemory)
             cmsSignalError(LCMS_ERRC_ABORTED, "Corrupted memory profile");              
       else
             cmsSignalError(LCMS_ERRC_ABORTED, "Corrupted profile: '%s'", Icc->PhysicalFile);

      
        _cmsFree(Icc);
       return NULL;
}




static
unsigned int uipow(unsigned int a, unsigned int b) {
        unsigned int rv = 1;
        for (; b > 0; b--)
                rv *= a;
        return rv;
}



// Convert between notations.

#define TO16_TAB(x)      (WORD) (((x) << 8) | (x))


// LUT8 can come only in Lab space. There is a fatal flaw in
// converting from Lut8 to Lut16. Due to particular encoding 
// of Lab, different actions should be taken from input and 
// output Lab8 LUTS. For input, is as easy as applying a << 8,
// since numbers comes in fixed point. However, for output LUT
// things goes a bit more complex.... LUT 16 is supposed to
// have a domain of 0..ff00, so we should remap the LUT in order
// to get things working. Affected signatures are B2Axx tags, 
// preview and gamut.

// I do solve it by multiplying input matrix by:
//  
//  | 0xffff/0xff00   0                0              |
//  |       0         0xffff/0xff00    0              |
//  |       0         0                0xffff/0xff00  |
//
// The input values got then remapped to adequate domain

static
void FixLUT8(LPLUT Lut, icTagSignature sig, size_t nTabSize)
{   
    MAT3 Fixup, Original, Result;
    LPWORD PtrW;
    size_t i;

    switch (sig) {
 

       case icSigBToA0Tag:
       case icSigBToA1Tag:
       case icSigBToA2Tag:
       case icSigGamutTag:
       case icSigPreview0Tag:
       case icSigPreview1Tag:
       case icSigPreview2Tag: 
               
           
                VEC3init(&Fixup.v[0], (double) 0xFFFF/0xFF00, 0, 0);
                VEC3init(&Fixup.v[1], 0, (double) 0xFFFF/0xFF00, 0);
                VEC3init(&Fixup.v[2], 0, 0, (double) 0xFFFF/0xFF00);
                          

                MAT3fromFix(&Original, &Lut->Matrix);               
                MAT3per(&Result, &Original, &Fixup);
                MAT3toFix(&Lut->Matrix, &Result);

                Lut -> wFlags |= LUT_HASMATRIX;
                break;

       // For input, clear low part since this has to be
       // Lab in fixed point

       default:        

                PtrW = Lut -> T;
                for (i = 0; i < nTabSize; i++) {

                             *PtrW++ &= 0xFF00;
                }
    }
    
}

// On Lab -> Lab abstract or Lab identities, fix both sides of LUT

static
void FixLUT8bothSides(LPLUT Lut, size_t nTabSize)
{
    MAT3 Fixup, Original, Result;
    LPWORD PtrW;
    size_t i;

        VEC3init(&Fixup.v[0], (double) 0xFFFF/0xFF00, 0, 0);
        VEC3init(&Fixup.v[1], 0, (double) 0xFFFF/0xFF00, 0);
        VEC3init(&Fixup.v[2], 0, 0, (double) 0xFFFF/0xFF00);
                  
        MAT3fromFix(&Original, &Lut->Matrix);               
        MAT3per(&Result, &Original, &Fixup);
        MAT3toFix(&Lut->Matrix, &Result);

        Lut -> wFlags |= LUT_HASMATRIX;

        PtrW = Lut -> T;
        for (i = 0; i < nTabSize; i++) {

                     *PtrW++ &= 0xFF00;
        }
    
}


// The infamous LUT 8

static
LCMSBOOL ReadLUT8(LPLCMSICCPROFILE Icc, LPLUT NewLUT, icTagSignature sig)
{
    icLut8 LUT8;
    LPBYTE Temp;
    size_t nTabSize;
    unsigned int i, j;
    unsigned int AllLinear;
    LPWORD PtrW;

       if (Icc ->Read(&LUT8, sizeof(icLut8) - SIZEOF_UINT8_ALIGNED, 1, Icc) != 1) return FALSE;
       
       NewLUT -> wFlags        = LUT_HASTL1|LUT_HASTL2|LUT_HAS3DGRID;
       NewLUT -> cLutPoints    = LUT8.clutPoints;
       NewLUT -> InputChan     = LUT8.inputChan;
       NewLUT -> OutputChan    = LUT8.outputChan;
       NewLUT -> InputEntries  = 256;
       NewLUT -> OutputEntries = 256;

       // Do some checking
       if (NewLUT -> cLutPoints > 100) NewLUT ->cLutPoints = 100;
       if (NewLUT -> InputChan > MAXCHANNELS)  NewLUT -> InputChan = MAXCHANNELS;
       if (NewLUT -> OutputChan > MAXCHANNELS) NewLUT -> OutputChan = MAXCHANNELS;

       AdjustEndianess32((LPBYTE) &LUT8.e00);
       AdjustEndianess32((LPBYTE) &LUT8.e01);
       AdjustEndianess32((LPBYTE) &LUT8.e02);
       AdjustEndianess32((LPBYTE) &LUT8.e10);
       AdjustEndianess32((LPBYTE) &LUT8.e11);
       AdjustEndianess32((LPBYTE) &LUT8.e12);
       AdjustEndianess32((LPBYTE) &LUT8.e20);
       AdjustEndianess32((LPBYTE) &LUT8.e21);
       AdjustEndianess32((LPBYTE) &LUT8.e22);


       // Matrix handling

       NewLUT -> Matrix.v[0].n[0] = (Fixed32) LUT8.e00;
       NewLUT -> Matrix.v[0].n[1] = (Fixed32) LUT8.e01;
       NewLUT -> Matrix.v[0].n[2] = (Fixed32) LUT8.e02;
       NewLUT -> Matrix.v[1].n[0] = (Fixed32) LUT8.e10;
       NewLUT -> Matrix.v[1].n[1] = (Fixed32) LUT8.e11;
       NewLUT -> Matrix.v[1].n[2] = (Fixed32) LUT8.e12;
       NewLUT -> Matrix.v[2].n[0] = (Fixed32) LUT8.e20;
       NewLUT -> Matrix.v[2].n[1] = (Fixed32) LUT8.e21;
       NewLUT -> Matrix.v[2].n[2] = (Fixed32) LUT8.e22;


       // Only operates if not identity...

       if ((NewLUT -> InputChan == 3) && !MAT3isIdentity(&NewLUT -> Matrix, 0.0001)) {

              NewLUT -> wFlags |= LUT_HASMATRIX;
       }


       // Copy input tables

       Temp = (LPBYTE) _cmsMalloc(256);
       if (Temp == NULL) return FALSE;

       AllLinear = 0;
       for (i=0; i < NewLUT -> InputChan; i++) {

              PtrW = (LPWORD) _cmsMalloc(sizeof(WORD) * 256);
              if (PtrW == NULL) {
                   _cmsFree(Temp);
                  return FALSE;
                  }

              NewLUT -> L1[i] = PtrW;
              if (Icc ->Read(Temp, 1, 256, Icc) != 256) {
                   _cmsFree(Temp);
                  return FALSE;
                  }

              for (j=0; j < 256; j++)
                     PtrW[j] = TO16_TAB(Temp[j]);
                     AllLinear += cmsIsLinear(NewLUT -> L1[i], NewLUT -> InputEntries);
              }

       // Linear input, so ignore full step

       if (AllLinear == NewLUT -> InputChan) {

              NewLUT -> wFlags &= ~LUT_HASTL1;
       }

        _cmsFree(Temp);

       // Copy 3D CLUT

       nTabSize = (NewLUT -> OutputChan * uipow(NewLUT->cLutPoints,
                                                NewLUT->InputChan));

       if (nTabSize > 0) {

            PtrW = (LPWORD) _cmsMalloc(sizeof(WORD) * nTabSize);
            if (PtrW == NULL) return FALSE;

            Temp = (LPBYTE) _cmsMalloc(nTabSize);
            if (Temp == NULL) {
                 _cmsFree(PtrW);
                return FALSE;
                }

            if (Icc ->Read(Temp, 1, nTabSize, Icc) != nTabSize) {
                 _cmsFree(Temp);
                _cmsFree(PtrW);
                return FALSE;
                }

            NewLUT -> T = PtrW;
            NewLUT -> Tsize = (unsigned int) (nTabSize * sizeof(WORD));

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

                     *PtrW++ = TO16_TAB(Temp[i]);
            }
            _cmsFree(Temp);
       }
       else {
           NewLUT ->T = NULL;
           NewLUT ->Tsize = 0;
           NewLUT ->wFlags &= ~LUT_HAS3DGRID;
       }


       // Copy output tables

       Temp = (LPBYTE) _cmsMalloc(256);
       if (Temp == NULL) {
           return FALSE;
           }

       AllLinear = 0;
       for (i=0; i < NewLUT -> OutputChan; i++) {

              PtrW = (LPWORD) _cmsMalloc(sizeof(WORD) * 256);
              if (PtrW == NULL) {
                  _cmsFree(Temp);
                  return FALSE;
                  }

              NewLUT -> L2[i] = PtrW;
              if (Icc ->Read(Temp, 1, 256, Icc) != 256) {
                  _cmsFree(Temp);
                  return FALSE;
                  }

              for (j=0; j < 256; j++)
                     PtrW[j] = TO16_TAB(Temp[j]);
                     AllLinear += cmsIsLinear(NewLUT -> L2[i], 256);
              }

       // Linear input, so ignore full step

       if (AllLinear == NewLUT -> OutputChan) {

              NewLUT -> wFlags &= ~LUT_HASTL2;
       }


       _cmsFree(Temp);

       cmsCalcL16Params(NewLUT -> InputEntries,  &NewLUT -> In16params);
       cmsCalcL16Params(NewLUT -> OutputEntries, &NewLUT -> Out16params);
       cmsCalcCLUT16Params(NewLUT -> cLutPoints,  NewLUT -> InputChan,
                                                  NewLUT -> OutputChan,
                                                 &NewLUT -> CLut16params);
       // Fixup
       
       if (Icc ->PCS == icSigLabData) {
           
           // Abstract or Lab identity

           if (Icc -> ColorSpace == icSigLabData) 

                FixLUT8bothSides(NewLUT, nTabSize);
           else
                FixLUT8(NewLUT, sig, nTabSize);


           // Now some additional fixup. Lab encoding on 8 bit makes
           // impossible to place gray axis on a exact node. However, 
           // some profiles does claim to do that. Poor lcms will try
           // to detect such condition and fix up "on the fly".
          
           {
               LPWORD WhiteLab, ExpectedWhite;
               WORD WhiteFixed[MAXCHANNELS], WhiteUnfixed[MAXCHANNELS];
               int j, nChannels;
               double Dist, DistFixed, DistUnfixed;
               
               _cmsEndPointsBySpace(icSigLabData, &WhiteLab, NULL, NULL);

               if (_cmsEndPointsBySpace(Icc -> ColorSpace, 
                                &ExpectedWhite, NULL, &nChannels)) {
                   
                   // 1.- Find white obtained by both combinations
                   
                   NewLUT -> FixGrayAxes = FALSE;
                   cmsEvalLUT(NewLUT, WhiteLab, WhiteUnfixed);
                   
                   NewLUT -> FixGrayAxes = TRUE;
                   cmsEvalLUT(NewLUT, WhiteLab, WhiteFixed);
                   
                   // 2.- Which method gives closer white?
                   
                   DistFixed = DistUnfixed = 0;
                   for (j=0; j < nChannels; j++) {
                       
                       Dist = ExpectedWhite[j] - WhiteFixed[j];
                       DistFixed += Dist*Dist;
                       Dist = ExpectedWhite[j] - WhiteUnfixed[j];
                       DistUnfixed += Dist*Dist;
                   }
                   
                   // 3.- Decide method
                   
                   if (sqrt(DistFixed) < sqrt(DistUnfixed))
                       NewLUT -> FixGrayAxes = TRUE;
                   else
                       NewLUT -> FixGrayAxes = FALSE;
               }
               
           }
           
       }

       return TRUE;
}




// Case LUT 16

static
LCMSBOOL ReadLUT16(LPLCMSICCPROFILE Icc, LPLUT NewLUT)
{
    icLut16 LUT16;
    size_t nTabSize;
    unsigned int i;
    unsigned int AllLinear;
    LPWORD PtrW;


       if (Icc ->Read(&LUT16, sizeof(icLut16)- SIZEOF_UINT16_ALIGNED, 1, Icc) != 1) 
            return FALSE;

       NewLUT -> wFlags        = LUT_HASTL1 | LUT_HASTL2 | LUT_HAS3DGRID;
       NewLUT -> cLutPoints    = LUT16.clutPoints;
       NewLUT -> InputChan     = LUT16.inputChan;
       NewLUT -> OutputChan    = LUT16.outputChan;

       AdjustEndianess16((LPBYTE) &LUT16.inputEnt);
       AdjustEndianess16((LPBYTE) &LUT16.outputEnt);

       NewLUT -> InputEntries  = LUT16.inputEnt;
       NewLUT -> OutputEntries = LUT16.outputEnt;


       // Matrix handling

       AdjustEndianess32((LPBYTE) &LUT16.e00);
       AdjustEndianess32((LPBYTE) &LUT16.e01);
       AdjustEndianess32((LPBYTE) &LUT16.e02);
       AdjustEndianess32((LPBYTE) &LUT16.e10);
       AdjustEndianess32((LPBYTE) &LUT16.e11);
       AdjustEndianess32((LPBYTE) &LUT16.e12);
       AdjustEndianess32((LPBYTE) &LUT16.e20);
       AdjustEndianess32((LPBYTE) &LUT16.e21);
       AdjustEndianess32((LPBYTE) &LUT16.e22);

       NewLUT -> Matrix.v[0].n[0] = (Fixed32) LUT16.e00;
       NewLUT -> Matrix.v[0].n[1] = (Fixed32) LUT16.e01;
       NewLUT -> Matrix.v[0].n[2] = (Fixed32) LUT16.e02;
       NewLUT -> Matrix.v[1].n[0] = (Fixed32) LUT16.e10;
       NewLUT -> Matrix.v[1].n[1] = (Fixed32) LUT16.e11;
       NewLUT -> Matrix.v[1].n[2] = (Fixed32) LUT16.e12;
       NewLUT -> Matrix.v[2].n[0] = (Fixed32) LUT16.e20;
       NewLUT -> Matrix.v[2].n[1] = (Fixed32) LUT16.e21;
       NewLUT -> Matrix.v[2].n[2] = (Fixed32) LUT16.e22;

       // Only operates if not identity...

       if ((NewLUT -> InputChan == 3) && !MAT3isIdentity(&NewLUT -> Matrix, 0.0001)) {

              NewLUT -> wFlags |= LUT_HASMATRIX;
       }


       // Copy input tables

       AllLinear = 0;
       for (i=0; i < NewLUT -> InputChan; i++) {

              PtrW = (LPWORD) _cmsMalloc(sizeof(WORD) * NewLUT -> InputEntries);
              if (PtrW == NULL) return FALSE;

              NewLUT -> L1[i] = PtrW;
              if (Icc ->Read(PtrW, sizeof(WORD), NewLUT -> InputEntries, Icc) != NewLUT -> InputEntries) {                 
                  return FALSE;
                  }

              AdjustEndianessArray16(PtrW, NewLUT -> InputEntries);
              AllLinear += cmsIsLinear(NewLUT -> L1[i], NewLUT -> InputEntries);
              }

       // Linear input, so ignore full step

       if (AllLinear == NewLUT -> InputChan) {

              NewLUT -> wFlags &= ~LUT_HASTL1;
       }


       // Copy 3D CLUT

       nTabSize = (NewLUT -> OutputChan * uipow(NewLUT->cLutPoints,
                                                NewLUT->InputChan));
       if (nTabSize > 0) {

           PtrW = (LPWORD) _cmsMalloc(sizeof(WORD) * nTabSize);
           if (PtrW == NULL) {
               _cmsFree(PtrW);
               return FALSE;
           }

           NewLUT -> T = PtrW;
           NewLUT -> Tsize = (unsigned int) (nTabSize * sizeof(WORD));

           if (Icc -> Read(PtrW, sizeof(WORD), nTabSize, Icc) != nTabSize) {
               return FALSE;
           }

           AdjustEndianessArray16(NewLUT -> T, nTabSize);
       }
       else {
           NewLUT ->T = NULL;
           NewLUT ->Tsize = 0;
           NewLUT -> wFlags &= ~LUT_HAS3DGRID;
       }

       // Copy output tables

       AllLinear = 0;
       for (i=0; i < NewLUT -> OutputChan; i++) {

              PtrW = (LPWORD) _cmsMalloc(sizeof(WORD) * NewLUT -> OutputEntries);
              if (PtrW == NULL) {
                  return FALSE;
                  }

              NewLUT -> L2[i] = PtrW;
              if (Icc ->Read(PtrW, sizeof(WORD), NewLUT -> OutputEntries, Icc) != NewLUT -> OutputEntries) {
                  return FALSE;
                  }

              AdjustEndianessArray16(PtrW, NewLUT -> OutputEntries);
              AllLinear += cmsIsLinear(NewLUT -> L2[i], NewLUT -> OutputEntries);
              }

       // Linear output, ignore step

       if (AllLinear == NewLUT -> OutputChan)
       {
              NewLUT -> wFlags &= ~LUT_HASTL2;
       }


       cmsCalcL16Params(NewLUT -> InputEntries,  &NewLUT -> In16params);
       cmsCalcL16Params(NewLUT -> OutputEntries, &NewLUT -> Out16params);
       cmsCalcCLUT16Params(NewLUT -> cLutPoints,  NewLUT -> InputChan,
                                                  NewLUT -> OutputChan,
                                                  &NewLUT -> CLut16params);

       return TRUE;
}


// This is a shared routine for reading curves. It can handle v2 curves
// as linear, single gamma and table-based as well as v4 parametric curves.

static
LPGAMMATABLE ReadCurve(LPLCMSICCPROFILE  Icc)
{    
    icUInt32Number      Count;
    LPGAMMATABLE        NewGamma;    
    icTagTypeSignature  BaseType;
    int                 n;


       BaseType = ReadBase(Icc);       
       switch (BaseType) {


       case ((icTagTypeSignature) 0x9478ee00):    // Monaco 2 profiler is BROKEN!
       case icSigCurveType:

           if (Icc ->Read(&Count, sizeof(icUInt32Number), 1, Icc) != 1) return NULL;
           AdjustEndianess32((LPBYTE) &Count);

           switch (Count) {

           case 0:   // Linear.

                     NewGamma = cmsAllocGamma(2);
                     if (!NewGamma) return NULL;
                     NewGamma -> GammaTable[0] = 0;
                     NewGamma -> GammaTable[1] = 0xFFFF;
                     return NewGamma;

           case 1:  // Specified as the exponent of gamma function
                    {
                     WORD SingleGammaFixed;

                     if (Icc ->Read(&SingleGammaFixed, sizeof(WORD), 1, Icc) != 1) return NULL;
                     AdjustEndianess16((LPBYTE) &SingleGammaFixed);
                     return cmsBuildGamma(4096, Convert8Fixed8(SingleGammaFixed));
                     }

           default: { // Curve
                    
                     NewGamma = cmsAllocGamma(Count);
                     if (!NewGamma) return NULL;

                     if (Icc ->Read(NewGamma -> GammaTable, sizeof(WORD), Count, Icc) != Count) 
                         return NULL;                    
                     AdjustEndianessArray16(NewGamma -> GammaTable, Count);
                     return NewGamma;
                    }
              }
              break;

    
       // Parametric curves
       case icSigParametricCurveType: {
           
           int ParamsByType[] = { 1, 3, 4, 5, 7 };
           double Params[10];
           icS15Fixed16Number Num;
           icUInt32Number Reserved;
           icUInt16Number   Type;
           int i;
           
           if (Icc -> Read(&Type, sizeof(icUInt16Number), 1, Icc) != 1) return NULL;
           if (Icc -> Read(&Reserved, sizeof(icUInt16Number), 1, Icc) != 1) return NULL;
           
           AdjustEndianess16((LPBYTE) &Type);
           if (Type > 4) {

                cmsSignalError(LCMS_ERRC_ABORTED, "Unknown parametric curve type '%d' found.", Type);
                return NULL;
           }
        
          ZeroMemory(Params, 10* sizeof(double));
          n = ParamsByType[Type];

          for (i=0; i < n; i++) {
                Num = 0;
                if (Icc -> Read(&Num, sizeof(icS15Fixed16Number), 1, Icc) != 1) return NULL;
                Params[i] = Convert15Fixed16(Num);
          }


           NewGamma = cmsBuildParametricGamma(4096, Type+1, Params);
           return NewGamma;
          }
    

       default:
              cmsSignalError(LCMS_ERRC_ABORTED, "Bad tag signature '%lx' found.", BaseType);
              return NULL;
       }
    
}


// Similar to anterior, but curve is reversed

static
LPGAMMATABLE ReadCurveReversed(LPLCMSICCPROFILE Icc)
{
     
     icTagTypeSignature BaseType;     
     LPGAMMATABLE       NewGamma, ReturnGamma;
     icUInt32Number     Count;
     int                n;
      

       BaseType = ReadBase(Icc);
       
       switch (BaseType) {


       case 0x9478ee00L:    // Monaco 2 profiler is BROKEN!
       case icSigCurveType:

           if (Icc -> Read(&Count, sizeof(icUInt32Number), 1, Icc) != 1) return NULL;
           AdjustEndianess32((LPBYTE) &Count);


           switch (Count) {

           case 0:   // Linear, reverse is same.

                     NewGamma = cmsAllocGamma(2);
                     if (!NewGamma) return NULL;

                     NewGamma -> GammaTable[0] = 0;
                     NewGamma -> GammaTable[1] = 0xFFFF;
                     return NewGamma;

           case 1:  {
                     WORD SingleGammaFixed;

                     if (Icc -> Read(&SingleGammaFixed, sizeof(WORD), 1, Icc) != 1) return NULL;
                     AdjustEndianess16((LPBYTE) &SingleGammaFixed);
                     return cmsBuildGamma(4096, 1./Convert8Fixed8(SingleGammaFixed));
                     }

           default: { // Curve. Do our best to trying to reverse the curve
                                         
                     NewGamma = cmsAllocGamma(Count);
                     if (!NewGamma) return NULL;

                     if (Icc -> Read(NewGamma -> GammaTable, sizeof(WORD), Count, Icc) != Count)
                         return NULL;
                     
                     AdjustEndianessArray16(NewGamma -> GammaTable, Count);

                     if (Count < 256) 
                         Count = 256;      // Reverse of simple curve has not necesarely to be simple

                     ReturnGamma = cmsReverseGamma(Count, NewGamma);
                     cmsFreeGamma(NewGamma);

                     return ReturnGamma;
                    }
              }
              break;

    
       // Parametric curves
       case icSigParametricCurveType: {
           
           int ParamsByType[] = { 1, 3, 4, 5, 7 };
           double Params[10];
           icS15Fixed16Number Num;
           icUInt32Number Reserved;           
           icUInt16Number   Type;
           int i;


           if (Icc -> Read(&Type, sizeof(icUInt16Number), 1, Icc) != 1) return NULL;
           if (Icc -> Read(&Reserved, sizeof(icUInt16Number), 1, Icc) != 1) return NULL;
           
           AdjustEndianess16((LPBYTE) &Type);
           if (Type > 4) {

                cmsSignalError(LCMS_ERRC_ABORTED, "Unknown parametric curve type '%d' found.", Type);
                return NULL;
           }
        
          ZeroMemory(Params, 10* sizeof(double));
          n = ParamsByType[Type];

          for (i=0; i < n; i++) {
                if (Icc -> Read(&Num, sizeof(icS15Fixed16Number), 1, Icc) != 1) return NULL;
                Params[i] = Convert15Fixed16(Num);
          }


           // Negative type as a mark of reversed curve
           NewGamma = cmsBuildParametricGamma(4096, -(Type+1), Params);
           return NewGamma;
          }
    

       default:
              cmsSignalError(LCMS_ERRC_ABORTED, "Bad tag signature '%lx' found.", BaseType);
              return NULL;
       }

}


// V4 stuff. Read matrix for LutAtoB and LutBtoA

static
LCMSBOOL ReadMatrixOffset(LPLCMSICCPROFILE Icc, size_t Offset, LPLUT NewLUT, DWORD dwFlags)
{

    icS15Fixed16Number All[12];
    int i;
    MAT3 m;
    VEC3 o;

    if (Icc -> Seek(Icc, Offset)) return FALSE;

    if (Icc ->Read(All, sizeof(icS15Fixed16Number), 12, Icc) != 12) 
        return FALSE;

    for (i=0; i < 12;  i++)
              AdjustEndianess32((LPBYTE) &All[i]);


       m.v[0].n[0] = FIXED_TO_DOUBLE((Fixed32) All[0]);
       m.v[0].n[1] = FIXED_TO_DOUBLE((Fixed32) All[1]);
       m.v[0].n[2] = FIXED_TO_DOUBLE((Fixed32) All[2]);
       m.v[1].n[0] = FIXED_TO_DOUBLE((Fixed32) All[3]);
       m.v[1].n[1] = FIXED_TO_DOUBLE((Fixed32) All[4]);
       m.v[1].n[2] = FIXED_TO_DOUBLE((Fixed32) All[5]);
       m.v[2].n[0] = FIXED_TO_DOUBLE((Fixed32) All[6]);
       m.v[2].n[1] = FIXED_TO_DOUBLE((Fixed32) All[7]);
       m.v[2].n[2] = FIXED_TO_DOUBLE((Fixed32) All[8]);

       o.n[0] = FIXED_TO_DOUBLE((Fixed32) All[9]);
       o.n[1] = FIXED_TO_DOUBLE((Fixed32) All[10]);
       o.n[2] = FIXED_TO_DOUBLE((Fixed32) All[11]);

       cmsSetMatrixLUT4(NewLUT, &m, &o, dwFlags);

       return TRUE;
}


//  V4 stuff. Read CLUT part for LutAtoB and LutBtoA

static
LCMSBOOL ReadCLUT(LPLCMSICCPROFILE Icc, size_t Offset, LPLUT NewLUT)
{

    icCLutStruct CLUT;

    if (Icc -> Seek(Icc, Offset)) return FALSE;
    if (Icc ->Read(&CLUT, sizeof(icCLutStruct), 1, Icc) != 1) return FALSE;


    cmsAlloc3DGrid(NewLUT, CLUT.gridPoints[0], NewLUT ->InputChan, 
                                               NewLUT ->OutputChan);

    // Precission can be 1 or 2 bytes

    if (CLUT.prec == 1) {

        BYTE v;
        unsigned int i;

        for (i=0; i < NewLUT->Tsize / sizeof(WORD); i++) {
                if (Icc ->Read(&v, sizeof(BYTE), 1, Icc) != 1) return FALSE;
                NewLUT->T[i] = TO16_TAB(v);
        }
                
    }
    else  
        if (CLUT.prec == 2) {
        
         size_t n = NewLUT->Tsize / sizeof(WORD);

         if (Icc ->Read(NewLUT ->T, sizeof(WORD), n, Icc) != n) return FALSE;
         AdjustEndianessArray16(NewLUT ->T, NewLUT->Tsize / sizeof(WORD));
    }
    else {
        cmsSignalError(LCMS_ERRC_ABORTED, "Unknow precission of '%d'", CLUT.prec); 
        return FALSE;
    }

    return TRUE;
}


static
void SkipAlignment(LPLCMSICCPROFILE Icc)
{
    BYTE Buffer[4];
    size_t At = Icc ->Tell(Icc);
    int BytesToNextAlignedPos = (int) (At % 4);
    
    Icc ->Read(Buffer, 1, BytesToNextAlignedPos, Icc);
}

// Read a set of curves from specific offset
static
LCMSBOOL ReadSetOfCurves(LPLCMSICCPROFILE Icc, size_t Offset, LPLUT NewLUT, int nLocation)
{     
    LPGAMMATABLE Curves[MAXCHANNELS];
    unsigned int i, nCurves;

    if (Icc -> Seek(Icc, Offset)) return FALSE;
    
    if (nLocation == 1 ||  nLocation == 3) 

        nCurves = NewLUT ->InputChan;    
    else 
        nCurves = NewLUT ->OutputChan;    

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

        Curves[i] = ReadCurve(Icc);                     
        if (Curves[i] == NULL) return FALSE;
        SkipAlignment(Icc);
   
    }
    
    NewLUT = cmsAllocLinearTable(NewLUT, Curves, nLocation);
    
    for (i=0; i < nCurves; i++) 
        cmsFreeGamma(Curves[i]);

    return TRUE;

}

// V4 stuff. LutAtoB type 
//
//  [L1] -> [CLUT] -> [L4] -> [Mat4] -> [Ofs4] -> [L2]
//
//  Mat, Mat3, Ofs3, L3 are missing
//   L1 = A curves
//   L4 = M curves
//   L2 = B curves

static
LCMSBOOL ReadLUT_A2B(LPLCMSICCPROFILE Icc, LPLUT NewLUT, size_t BaseOffset, icTagSignature sig)
{
    icLutAtoB LUT16;
     
       if (Icc ->Read(&LUT16, sizeof(icLutAtoB), 1, Icc) != 1) return FALSE;

       NewLUT -> InputChan     = LUT16.inputChan;
       NewLUT -> OutputChan    = LUT16.outputChan;

       AdjustEndianess32((LPBYTE) &LUT16.offsetB);
       AdjustEndianess32((LPBYTE) &LUT16.offsetMat);
       AdjustEndianess32((LPBYTE) &LUT16.offsetM);
       AdjustEndianess32((LPBYTE) &LUT16.offsetC);
       AdjustEndianess32((LPBYTE) &LUT16.offsetA);
               
       if (LUT16.offsetB != 0)                                         
                ReadSetOfCurves(Icc, BaseOffset + LUT16.offsetB, NewLUT, 2);

       if (LUT16.offsetMat != 0)            
            ReadMatrixOffset(Icc, BaseOffset + LUT16.offsetMat, NewLUT, LUT_HASMATRIX4);
       

       if (LUT16.offsetM != 0) 
                ReadSetOfCurves(Icc, BaseOffset + LUT16.offsetM, NewLUT, 4);

       if (LUT16.offsetC != 0) 
           ReadCLUT(Icc, BaseOffset + LUT16.offsetC, NewLUT);

       if (LUT16.offsetA!= 0) 
                ReadSetOfCurves(Icc, BaseOffset + LUT16.offsetA, NewLUT, 1);

        // Convert to v2 PCS

       if (Icc ->PCS == icSigLabData) {

       switch (sig) {

       case icSigAToB0Tag:
       case icSigAToB1Tag:
       case icSigAToB2Tag:
       case icSigGamutTag:
       case icSigPreview0Tag:
       case icSigPreview1Tag:
       case icSigPreview2Tag: 

               NewLUT ->wFlags |= LUT_V4_INPUT_EMULATE_V2;
               break;

       default:;
       }
       }

       
       return TRUE;
}

// V4 stuff. LutBtoA type 

static
LCMSBOOL ReadLUT_B2A(LPLCMSICCPROFILE Icc, LPLUT NewLUT,  size_t BaseOffset, icTagSignature sig)
{ 
  icLutBtoA LUT16;
     
       if (Icc ->Read(&LUT16, sizeof(icLutBtoA), 1, Icc) != 1) return FALSE;

       NewLUT -> InputChan     = LUT16.inputChan;
       NewLUT -> OutputChan    = LUT16.outputChan;

       AdjustEndianess32((LPBYTE) &LUT16.offsetB);
       AdjustEndianess32((LPBYTE) &LUT16.offsetMat);
       AdjustEndianess32((LPBYTE) &LUT16.offsetM);
       AdjustEndianess32((LPBYTE) &LUT16.offsetC);
       AdjustEndianess32((LPBYTE) &LUT16.offsetA);
           
      
       if (LUT16.offsetB != 0)                                         
                ReadSetOfCurves(Icc, BaseOffset + LUT16.offsetB, NewLUT, 1);

       if (LUT16.offsetMat != 0)            
            ReadMatrixOffset(Icc, BaseOffset + LUT16.offsetMat, NewLUT, LUT_HASMATRIX3);
      
       if (LUT16.offsetM != 0) 
                ReadSetOfCurves(Icc, BaseOffset + LUT16.offsetM, NewLUT, 3);

       if (LUT16.offsetC != 0) 
           ReadCLUT(Icc, BaseOffset + LUT16.offsetC, NewLUT);

       if (LUT16.offsetA!= 0) 
                ReadSetOfCurves(Icc, BaseOffset + LUT16.offsetA, NewLUT, 2);
      

       // Convert to v2 PCS

       if (Icc ->PCS == icSigLabData) {

       switch (sig) {

       case icSigBToA0Tag:
       case icSigBToA1Tag:
       case icSigBToA2Tag:
       case icSigGamutTag:
       case icSigPreview0Tag:
       case icSigPreview1Tag:
       case icSigPreview2Tag: 

               NewLUT ->wFlags |= LUT_V4_OUTPUT_EMULATE_V2;
               break;

       default:;
       }
       }

       return TRUE;
}

// CLUT main reader

LPLUT LCMSEXPORT cmsReadICCLut(cmsHPROFILE hProfile, icTagSignature sig)
{

    LPLCMSICCPROFILE    Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
    icTagTypeSignature  BaseType;    
    int                 n;
    size_t              offset;
    LPLUT               NewLUT;

    n = _cmsSearchTag(Icc, sig, TRUE);
    if (n < 0)      
        return NULL;
    

    // If is in memory, the LUT is already there, so throw a copy
    if (Icc -> TagPtrs[n]) {
                
        return cmsDupLUT((LPLUT) Icc ->TagPtrs[n]);
    }

    offset = Icc -> TagOffsets[n];

    if (Icc -> Seek(Icc, offset))
            return NULL;

    BaseType = ReadBase(Icc);

    
    NewLUT = cmsAllocLUT();
    if (!NewLUT) {

       cmsSignalError(LCMS_ERRC_ABORTED, "cmsAllocLUT() failed");
       return NULL;
    }


    switch (BaseType) {

    case icSigLut8Type:    if (!ReadLUT8(Icc, NewLUT, sig)) {
                                cmsFreeLUT(NewLUT);
                                return NULL;
                           }
                           break;

    case icSigLut16Type:   if (!ReadLUT16(Icc, NewLUT)) {
                                cmsFreeLUT(NewLUT);
                                return NULL;
                           }
                           break;

    case icSiglutAtoBType: if (!ReadLUT_A2B(Icc, NewLUT, offset, sig)) {
                                cmsFreeLUT(NewLUT);
                                return NULL;
                           }
                           break;

    case icSiglutBtoAType: if (!ReadLUT_B2A(Icc, NewLUT, offset, sig)) {
                                cmsFreeLUT(NewLUT);
                                return NULL;
                           }
                           break;

    default:  cmsSignalError(LCMS_ERRC_ABORTED, "Bad tag signature %lx found.", BaseType);
              cmsFreeLUT(NewLUT);
              return NULL;
    }


    return NewLUT;
}


// Sets the language & country preferences. Used only in ICC 4.0 profiles

void LCMSEXPORT cmsSetLanguage(const char LanguageCode[4], const char CountryCode[4])
{

    int LanguageCodeInt = *(int *) LanguageCode;
    int CountryCodeInt  = *(int *) CountryCode;
    
    AdjustEndianess32((LPBYTE) &LanguageCodeInt);
    AdjustEndianess32((LPBYTE) &CountryCodeInt);

    GlobalLanguageCode = LanguageCodeInt;
    GlobalCountryCode  = CountryCodeInt;
}



// Some tags (e.g, 'pseq') can have text tags embedded. This function
// handles such special case. Returns -1 on error, or the number of bytes left on success. 

static
int ReadEmbeddedTextTag(LPLCMSICCPROFILE Icc, size_t size, char* Name, size_t size_max)
{
    icTagTypeSignature  BaseType;
           

    BaseType = ReadBase(Icc);        
    size -= sizeof(icTagBase);
    
    switch (BaseType) {

    case icSigTextDescriptionType: {

           icUInt32Number  AsciiCount;
           icUInt32Number  i, UnicodeCode, UnicodeCount;
           icUInt16Number  ScriptCodeCode, Dummy;
           icUInt8Number   ScriptCodeCount;
           
           if (Icc ->Read(&AsciiCount, sizeof(icUInt32Number), 1, Icc) != 1) return -1;

           if (size < sizeof(icUInt32Number)) return (int) size;
           size -= sizeof(icUInt32Number);

           AdjustEndianess32((LPBYTE) &AsciiCount);
           Icc ->Read(Name, 1, 
                (AsciiCount >= size_max) ? (size_max-1) : AsciiCount, Icc);

           if (size < AsciiCount) return (int) size;
           size -= AsciiCount;

           // Skip Unicode code

           if (Icc ->Read(&UnicodeCode,  sizeof(icUInt32Number), 1, Icc) != 1) return -1;
           if (size < sizeof(icUInt32Number)) return (int) size;
           size -= sizeof(icUInt32Number);

           if (Icc ->Read(&UnicodeCount, sizeof(icUInt32Number), 1, Icc) != 1) return -1;
           if (size < sizeof(icUInt32Number)) return (int) size;
           size -= sizeof(icUInt32Number);

           AdjustEndianess32((LPBYTE) &UnicodeCount);

           if (UnicodeCount > size) return (int) size;

           for (i=0; i < UnicodeCount; i++) { 
                size_t nread = Icc ->Read(&Dummy, sizeof(icUInt16Number), 1, Icc);
                if (nread != 1) return (int) size;
                size -= sizeof(icUInt16Number);
           }
                       
          // Skip ScriptCode code

           if (Icc ->Read(&ScriptCodeCode,  sizeof(icUInt16Number), 1, Icc) != 1) return -1;
           size -= sizeof(icUInt16Number);
           if (Icc ->Read(&ScriptCodeCount, sizeof(icUInt8Number), 1, Icc) != 1) return -1;
           size -= sizeof(icUInt8Number);

           // Should remain 67 bytes as filler

           if (size < 67) return (int) size;

           for (i=0; i < 67; i++) {
                size_t nread = Icc ->Read(&Dummy, sizeof(icUInt8Number), 1, Icc);
                if (nread != 1) return (int) size;
                size --;
               }           
           }
           break;


    case icSigCopyrightTag:   // Broken profiles from agfa does store copyright info in such type
    case icSigTextType:
         {    
         char Dummy;   
         size_t i, Missing = 0;

         if (size >= size_max) {

             Missing = size - size_max + 1;
             size = size_max - 1;
         }

         if (Icc -> Read(Name, 1, size, Icc) != size) return -1;

         for (i=0; i < Missing; i++) 
             Icc -> Read(&Dummy, 1, 1, Icc);
         }
         break;

    // MultiLocalizedUnicodeType, V4 only

    case icSigMultiLocalizedUnicodeType: {

        icUInt32Number Count, RecLen;       
        icUInt16Number Language, Country;
        icUInt32Number ThisLen, ThisOffset;
        size_t         Offset = 0;
        size_t         Len    = 0;      
        size_t         i;
        wchar_t*       wchar  = L"";
                    

            if (Icc ->Read(&Count, sizeof(icUInt32Number), 1, Icc) != 1) return -1;
            AdjustEndianess32((LPBYTE) &Count);
            if (Icc ->Read(&RecLen, sizeof(icUInt32Number), 1, Icc) != 1) return -1;
            AdjustEndianess32((LPBYTE) &RecLen);

            if (RecLen != 12) {

                    cmsSignalError(LCMS_ERRC_ABORTED, "multiLocalizedUnicodeType of len != 12 is not supported.");
                    return -1;
            }

            for (i=0; i < Count; i++) {
                
                if (Icc ->Read(&Language, sizeof(icUInt16Number), 1, Icc) != 1) return -1;
                AdjustEndianess16((LPBYTE) &Language);
                if (Icc ->Read(&Country, sizeof(icUInt16Number), 1, Icc) != 1) return -1;
                AdjustEndianess16((LPBYTE) &Country);
    
                if (Icc ->Read(&ThisLen, sizeof(icUInt32Number), 1, Icc) != 1) return -1;
                AdjustEndianess32((LPBYTE) &ThisLen);
    
                if (Icc ->Read(&ThisOffset, sizeof(icUInt32Number), 1, Icc) != 1) return -1;
                AdjustEndianess32((LPBYTE) &ThisOffset);
    
                if (Language == GlobalLanguageCode || Offset == 0) {

                    Len = ThisLen; Offset = ThisOffset; 
                    if (Country == GlobalCountryCode) 
                                    break;              // Found                        
                }
                        
            }
            

            if (Offset == 0) {

                    strcpy(Name, "(no info)");
                    break;
            }
            
            // Compute true offset
            Offset -= 12 * Count + 8 + sizeof(icTagBase);

            // Skip unused bytes
            for (i=0; i < Offset; i++) {
                    
                    char Discard;
                    Icc ->Read(&Discard, 1, 1, Icc);
            }


            // Bound len
            if (Len < 0) Len = 0;
            if (Len > 20*1024) Len = 20 * 1024; 

            wchar = (wchar_t*) _cmsMalloc(Len+2);
            if (!wchar) return -1;
            
            if (Icc ->Read(wchar, 1, Len, Icc) != Len) return -1;
            AdjustEndianessArray16((LPWORD) wchar, Len / 2);

            wchar[Len / 2] = L'\0';
            i = wcstombs(Name, wchar, size_max );  
            if (i == ((size_t) -1)) {

                Name[0] = 0;    // Error
            }

            _cmsFree((void*) wchar);
            }
            break;

    default:
            cmsSignalError(LCMS_ERRC_ABORTED, "Bad tag signature %lx found.", BaseType);
            return -1;
    }

    return (int) size;
}


// Take an ASCII item. Takes at most LCMS_DESC_MAX


int LCMSEXPORT cmsReadICCTextEx(cmsHPROFILE hProfile, icTagSignature sig, char *Name, size_t size_max)
{
    LPLCMSICCPROFILE    Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
    size_t              offset, size;
    int                 n;

    n = _cmsSearchTag(Icc, sig, TRUE);
    if (n < 0)         
        return -1;
    
    if (Icc -> TagPtrs[n]) {

        CopyMemory(Name, Icc -> TagPtrs[n], Icc -> TagSizes[n]);
        return (int) Icc -> TagSizes[n];
    }

    offset = Icc -> TagOffsets[n];
    size   = Icc -> TagSizes[n];

    if (Icc -> Seek(Icc, offset))
            return -1;
      
    return ReadEmbeddedTextTag(Icc, size, Name, size_max);
}

// Keep compatibility with older versions

int LCMSEXPORT cmsReadICCText(cmsHPROFILE hProfile, icTagSignature sig, char *Text)
{
    return cmsReadICCTextEx(hProfile, sig, Text, LCMS_DESC_MAX);
}


// Take an XYZ item

static
int ReadICCXYZ(cmsHPROFILE hProfile, icTagSignature sig, LPcmsCIEXYZ Value, LCMSBOOL lIsFatal)
{
    LPLCMSICCPROFILE    Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
    icTagTypeSignature  BaseType;    
    size_t              offset;
    int                 n;
    icXYZNumber         XYZ;

    n = _cmsSearchTag(Icc, sig, FALSE);
    if (n < 0)    
            return -1;
    
    if (Icc -> TagPtrs[n]) {

         CopyMemory(Value, Icc -> TagPtrs[n], Icc -> TagSizes[n]);       
         return (int) Icc -> TagSizes[n];
    }

    offset = Icc -> TagOffsets[n];

    if (Icc -> Seek(Icc, offset))
            return -1;

    
    BaseType = ReadBase(Icc);
    
    switch (BaseType) {

        
    case 0x7c3b10cL:    // Some apple broken embedded profiles does not have correct type   
    case icSigXYZType:

           Icc ->Read(&XYZ, sizeof(icXYZNumber), 1, Icc);
           Value -> X = Convert15Fixed16(XYZ.X);
           Value -> Y = Convert15Fixed16(XYZ.Y);
           Value -> Z = Convert15Fixed16(XYZ.Z);
           break;

    // Aug/21-2001 - Monaco 2 does have WRONG values.

    default:
           if (lIsFatal)
                cmsSignalError(LCMS_ERRC_ABORTED, "Bad tag signature %lx found.", BaseType);
           return -1;
    }

    return 1;
}


// Read a icSigS15Fixed16ArrayType (currently only a 3x3 matrix)           

static
int ReadICCXYZArray(cmsHPROFILE hProfile, icTagSignature sig, LPMAT3 v)
{
    LPLCMSICCPROFILE    Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;    
    icTagTypeSignature  BaseType; 
    size_t              offset, sz;
    int                 i, n;
    icXYZNumber         XYZ[3];
    cmsCIEXYZ           XYZdbl[3];


    n = _cmsSearchTag(Icc, sig, FALSE);
    if (n < 0)      
            return -1; // Not found
    
    if (Icc -> TagPtrs[n]) {

            CopyMemory(v, Icc -> TagPtrs[n], Icc -> TagSizes[n]);
            return (int) Icc -> TagSizes[n];
    }

    offset = Icc -> TagOffsets[n];

    if (Icc -> Seek(Icc, offset))
            return -1;
    
    BaseType = ReadBase(Icc);
   
    switch (BaseType) {

    case icSigS15Fixed16ArrayType:

         sz = Icc ->TagSizes[n] / sizeof(icXYZNumber);

         if (sz != 3) {
             cmsSignalError(LCMS_ERRC_ABORTED, "Bad array size of %d entries.", sz);
             return -1;
         }
                
         Icc ->Read(XYZ, sizeof(icXYZNumber), 3, Icc);

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

            XYZdbl[i].X = Convert15Fixed16(XYZ[i].X);
            XYZdbl[i].Y = Convert15Fixed16(XYZ[i].Y);
            XYZdbl[i].Z = Convert15Fixed16(XYZ[i].Z);
         }

         CopyMemory(v, XYZdbl, 3*sizeof(cmsCIEXYZ));
         break;

    default:
         cmsSignalError(LCMS_ERRC_ABORTED, "Bad tag signature %lx found.", BaseType);
         return -1;

    }

   return sizeof(MAT3);
}



// Primaries are to be in xyY notation

LCMSBOOL LCMSEXPORT cmsTakeColorants(LPcmsCIEXYZTRIPLE Dest, cmsHPROFILE hProfile)
{
       if (ReadICCXYZ(hProfile, icSigRedColorantTag, &Dest -> Red, TRUE) < 0) return FALSE;
       if (ReadICCXYZ(hProfile, icSigGreenColorantTag, &Dest -> Green, TRUE) < 0) return FALSE;
       if (ReadICCXYZ(hProfile, icSigBlueColorantTag, &Dest -> Blue, TRUE) < 0) return FALSE;

       return TRUE;
}


LCMSBOOL cmsReadICCMatrixRGB2XYZ(LPMAT3 r, cmsHPROFILE hProfile)
{
       cmsCIEXYZTRIPLE Primaries;

       if (!cmsTakeColorants(&Primaries, hProfile)) return FALSE;

       VEC3init(&r -> v[0], Primaries.Red.X, Primaries.Green.X,  Primaries.Blue.X);
       VEC3init(&r -> v[1], Primaries.Red.Y, Primaries.Green.Y,  Primaries.Blue.Y);
       VEC3init(&r -> v[2], Primaries.Red.Z, Primaries.Green.Z,  Primaries.Blue.Z);

       return TRUE;

}


// Always return a suitable matrix

LCMSBOOL cmsReadChromaticAdaptationMatrix(LPMAT3 r, cmsHPROFILE hProfile)
{    
      
    if (ReadICCXYZArray(hProfile, icSigChromaticAdaptationTag, r) < 0) {

       LPLCMSICCPROFILE  Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;

       // For display profiles, revert to bradford. Else take identity.

       MAT3identity(r);

       // Emissive devices have non-identity chad

       if ((cmsGetDeviceClass(hProfile) == icSigDisplayClass) ||
           cmsTakeHeaderFlags(hProfile) & icTransparency) {

            // NULL for cone defaults to Bradford, from media to D50
            cmsAdaptationMatrix(r, NULL, &Icc ->MediaWhitePoint, &Icc ->Illuminant);        
        }
    }
    
    return TRUE;
}



LPGAMMATABLE LCMSEXPORT cmsReadICCGamma(cmsHPROFILE hProfile, icTagSignature sig)
{
       LPLCMSICCPROFILE  Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
       size_t         offset;
       int            n;


       n = _cmsSearchTag(Icc, sig, TRUE);
       if (n < 0)   
           return NULL;
       
       if (Icc -> TagPtrs[n]) {

            return cmsDupGamma((LPGAMMATABLE) Icc -> TagPtrs[n]);
       }

       offset = Icc -> TagOffsets[n];

       if (Icc -> Seek(Icc, offset))
            return NULL;

       return ReadCurve(Icc);
     
}


// Some ways have analytical revese. This function accounts for that

LPGAMMATABLE LCMSEXPORT cmsReadICCGammaReversed(cmsHPROFILE hProfile, icTagSignature sig)
{
       LPLCMSICCPROFILE  Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
       size_t         offset;
       int            n;


       n = _cmsSearchTag(Icc, sig, TRUE);
       if (n < 0) 
            return NULL;       

       if (Icc -> TagPtrs[n]) {

            return cmsReverseGamma(256, (LPGAMMATABLE) Icc -> TagPtrs[n]);
       }

       offset = Icc -> TagOffsets[n];

       if (Icc -> Seek(Icc, offset))
            return NULL;
       
       return ReadCurveReversed(Icc);       
}

// Check Named color header

static
LCMSBOOL CheckHeader(LPcmsNAMEDCOLORLIST v, icNamedColor2* nc2)
{
    if (v ->Prefix[0] == 0 && v ->Suffix[0] == 0 && v ->ColorantCount == 0) return TRUE;

    if (stricmp(v ->Prefix, (const char*) nc2 ->prefix) != 0) return FALSE;
    if (stricmp(v ->Suffix, (const char*) nc2 ->suffix) != 0) return FALSE;

    return ((int) v ->ColorantCount == (int) nc2 ->nDeviceCoords);
}

// Read named color list

int cmsReadICCnamedColorList(cmsHTRANSFORM xform, cmsHPROFILE hProfile, icTagSignature sig)
{
       _LPcmsTRANSFORM v = (_LPcmsTRANSFORM) xform;
       LPLCMSICCPROFILE   Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
       int                n;       
       icTagTypeSignature BaseType;
       size_t             offset;
       
       n = _cmsSearchTag(Icc, sig, TRUE);
       if (n < 0)             
            return 0;
       
       if (Icc -> TagPtrs[n]) {
           
            // This replaces actual named color list.                       
            size_t size   = Icc -> TagSizes[n];  

            if (v ->NamedColorList) cmsFreeNamedColorList(v ->NamedColorList);
            v -> NamedColorList = (LPcmsNAMEDCOLORLIST) _cmsMalloc(size);           
            CopyMemory(v -> NamedColorList, Icc ->TagPtrs[n], size);    
            return v ->NamedColorList->nColors;           
       }

       offset = Icc -> TagOffsets[n];

       if (Icc -> Seek(Icc, offset))
            return 0;

       BaseType = ReadBase(Icc);
       
       switch (BaseType) {

        // I never have seen one of these. Probably is not worth of implementing.

       case icSigNamedColorType: {

              cmsSignalError(LCMS_ERRC_WARNING, "Ancient named color profiles are not supported.");
              return 0;                 
            }
           
        // The named color struct
        
       case icSigNamedColor2Type: {

                icNamedColor2 nc2;
                unsigned int i, j;
                                
                if (Icc -> Read(&nc2, sizeof(icNamedColor2) - SIZEOF_UINT8_ALIGNED, 1, Icc) != 1) return 0;
                AdjustEndianess32((LPBYTE) &nc2.vendorFlag);
                AdjustEndianess32((LPBYTE) &nc2.count);
                AdjustEndianess32((LPBYTE) &nc2.nDeviceCoords);

                if (!CheckHeader(v->NamedColorList, &nc2)) {
                     cmsSignalError(LCMS_ERRC_WARNING, "prefix/suffix/device for named color profiles mismatch.");
                     return 0;
                }

                if (nc2.nDeviceCoords > MAXCHANNELS) {
                          cmsSignalError(LCMS_ERRC_WARNING, "Too many device coordinates.");
                          return 0;
                }

                strncpy(v ->NamedColorList->Prefix, (const char*) nc2.prefix, 32);
                strncpy(v ->NamedColorList->Suffix, (const char*) nc2.suffix, 32);
                v ->NamedColorList->Prefix[32] = v->NamedColorList->Suffix[32] = 0;
                
                v ->NamedColorList ->ColorantCount = nc2.nDeviceCoords;
                
                for (i=0; i < nc2.count; i++) {

                    WORD PCS[3];
                    WORD Colorant[MAXCHANNELS];
                    char Root[33];

                    ZeroMemory(Colorant, sizeof(WORD) * MAXCHANNELS);
                    Icc -> Read(Root, 1, 32, Icc);
                    Icc -> Read(PCS,  3, sizeof(WORD), Icc);

                    for (j=0; j < 3; j++)
                        AdjustEndianess16((LPBYTE) &PCS[j]);
                    
                    Icc -> Read(Colorant, sizeof(WORD), nc2.nDeviceCoords, Icc);

                    for (j=0; j < nc2.nDeviceCoords; j++) 
                            AdjustEndianess16((LPBYTE) &Colorant[j]);
                    
                    cmsAppendNamedColor(v, Root, PCS, Colorant);
                }

            return v ->NamedColorList->nColors;
            }
            break;

       default:
              cmsSignalError(LCMS_ERRC_WARNING, "Bad tag signature '%lx' found.", BaseType);
              return 0;
       }

       // It would never reach here
       // return 0;
}



// Read colorant tables

LPcmsNAMEDCOLORLIST LCMSEXPORT cmsReadColorantTable(cmsHPROFILE hProfile, icTagSignature sig)
{   
    icInt32Number n, Count, i;
    size_t offset;
    icTagTypeSignature  BaseType;
    LPLCMSICCPROFILE   Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
    LPcmsNAMEDCOLORLIST List;

    n = _cmsSearchTag(Icc, sig, FALSE);
    if (n < 0)      
            return NULL; // Not found
    
    if (Icc -> TagPtrs[n]) {

            size_t size   = Icc -> TagSizes[n];  
            void* v = _cmsMalloc(size);
            
            if (v == NULL) return NULL;
            CopyMemory(v, Icc -> TagPtrs[n], size);
            return (LPcmsNAMEDCOLORLIST) v;
    }


    offset = Icc -> TagOffsets[n];

    if (Icc -> Seek(Icc, offset))
            return NULL;
    
    BaseType = ReadBase(Icc);

    if (BaseType != icSigColorantTableType) {
            cmsSignalError(LCMS_ERRC_ABORTED, "Bad tag signature '%lx' found.", BaseType);
            return NULL;
    }


    if (Icc ->Read(&Count, sizeof(icUInt32Number), 1, Icc) != 1) return NULL;
    AdjustEndianess32((LPBYTE) &Count); 

    if (Count > MAXCHANNELS) {
        cmsSignalError(LCMS_ERRC_ABORTED, "Too many colorants '%lx'", Count);
        return NULL;
    }

    List = cmsAllocNamedColorList(Count);
    for (i=0; i < Count; i++) {

        if (!Icc ->Read(List->List[i].Name, 1, 32 , Icc)) goto Error;
        if (!Icc ->Read(List->List[i].PCS, sizeof(icUInt16Number), 3, Icc)) goto Error;
        AdjustEndianessArray16(List->List[i].PCS, 3);       
    }

    return List;

Error:
    cmsFreeNamedColorList(List);
    return NULL;

}



// Uncooked manufacturer

const char* LCMSEXPORT cmsTakeManufacturer(cmsHPROFILE hProfile)
{

    static char Manufacturer[LCMS_DESC_MAX] = "";

       Manufacturer[0] = 0;   

       if (cmsIsTag(hProfile, icSigDeviceMfgDescTag)) {

            cmsReadICCTextEx(hProfile, icSigDeviceMfgDescTag, Manufacturer, LCMS_DESC_MAX);
       }

    return Manufacturer;
}

// Uncooked model

const char* LCMSEXPORT cmsTakeModel(cmsHPROFILE hProfile)
{

    static char Model[LCMS_DESC_MAX] = "";

       Model[0] = 0;   

       if (cmsIsTag(hProfile, icSigDeviceModelDescTag)) {

            cmsReadICCTextEx(hProfile, icSigDeviceModelDescTag, Model, LCMS_DESC_MAX);
       }

    return Model;
}


const char* LCMSEXPORT cmsTakeCopyright(cmsHPROFILE hProfile)
{

    static char Copyright[LCMS_DESC_MAX] = "";

       Copyright[0] = 0;   
       if (cmsIsTag(hProfile, icSigCopyrightTag)) {

            cmsReadICCTextEx(hProfile, icSigCopyrightTag, Copyright, LCMS_DESC_MAX);
       }

    return Copyright;
}


// We compute name with model - manufacturer

const char*  LCMSEXPORT cmsTakeProductName(cmsHPROFILE hProfile)
{
    static char Name[LCMS_DESC_MAX*2+4];
    char Manufacturer[LCMS_DESC_MAX], Model[LCMS_DESC_MAX];
    
    Name[0] = '\0';
    Manufacturer[0] = Model[0] = '\0';
    
    if (cmsIsTag(hProfile, icSigDeviceMfgDescTag)) {
        
        cmsReadICCTextEx(hProfile, icSigDeviceMfgDescTag, Manufacturer, LCMS_DESC_MAX);
    }
    
    if (cmsIsTag(hProfile, icSigDeviceModelDescTag)) {
        
        cmsReadICCTextEx(hProfile, icSigDeviceModelDescTag, Model, LCMS_DESC_MAX);
    }
    
    if (!Manufacturer[0] && !Model[0]) {
        
        if (cmsIsTag(hProfile, icSigProfileDescriptionTag)) {
            
            cmsReadICCTextEx(hProfile, icSigProfileDescriptionTag, Name, LCMS_DESC_MAX);
            return Name;
        }
        else return "{no name}";
    }
    
    
    if (!Manufacturer[0] || 
            strncmp(Model, Manufacturer, 8) == 0 || strlen(Model) > 30)
        strcpy(Name, Model);
    else
        sprintf(Name, "%s - %s", Model, Manufacturer);
    
    return Name;
    
}


// We compute desc with manufacturer - model

const char*  LCMSEXPORT cmsTakeProductDesc(cmsHPROFILE hProfile)
{
       static char Name[2048];

       if (cmsIsTag(hProfile, icSigProfileDescriptionTag)) {

              cmsReadICCText(hProfile, icSigProfileDescriptionTag, Name);
       }
       else return cmsTakeProductName(hProfile);

       if (strncmp(Name, "Copyrig", 7) == 0)
              return cmsTakeProductName(hProfile);

       return Name;
}


const char*  LCMSEXPORT cmsTakeProductInfo(cmsHPROFILE hProfile)
{       
       LPLCMSICCPROFILE  Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;

       static char Info[4096];

       Info[0] = '\0';

       if (cmsIsTag(hProfile, icSigProfileDescriptionTag))
       {
       char Desc[1024];

       cmsReadICCText(hProfile, icSigProfileDescriptionTag, Desc);
       strcat(Info, Desc);
       strcat(Info, "\r\n\r\n");
       }


       if (cmsIsTag(hProfile, icSigCopyrightTag))
       {
       char Copyright[LCMS_DESC_MAX];

       cmsReadICCText(hProfile, icSigCopyrightTag, Copyright);
       strcat(Info, Copyright);
       strcat(Info, "\r\n\r\n");
       }



// KODAK private tag... But very useful

#define K007         (icTagSignature)0x4B303037

       // MonCal

       if (cmsIsTag(hProfile, K007))
       {
       char MonCal[LCMS_DESC_MAX];

       cmsReadICCText(hProfile, K007, MonCal);
       strcat(Info, MonCal);
       strcat(Info, "\r\n\r\n");
       }
       else
       {
       cmsCIEXYZ WhitePt;
       char WhiteStr[1024];

       cmsTakeMediaWhitePoint(&WhitePt, hProfile);
       _cmsIdentifyWhitePoint(WhiteStr, &WhitePt);
       strcat(WhiteStr, "\r\n\r\n");
       strcat(Info, WhiteStr);
       }


       if (Icc -> stream) {
              strcat(Info, Icc -> PhysicalFile);
       }
       return Info;
}

// Extract the target data as a big string. Does not signal if tag is not present.

LCMSBOOL LCMSEXPORT cmsTakeCharTargetData(cmsHPROFILE hProfile, char** Data, size_t* len)
{
    LPLCMSICCPROFILE  Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
    int n;

    *Data = NULL;
    *len  = 0;

    n = _cmsSearchTag(Icc, icSigCharTargetTag, FALSE);
    if (n < 0) return FALSE;                


    *len =  Icc -> TagSizes[n];

    // Make sure that is reasonable (600K)
    if (*len > 600*1024) *len = 600*1024;

    *Data = (char*) _cmsMalloc(*len + 1);  // Plus zero marker

    if (!*Data) {

        cmsSignalError(LCMS_ERRC_ABORTED, "Out of memory allocating CharTarget space!");
        return FALSE;
    }

    if (cmsReadICCTextEx(hProfile, icSigCharTargetTag, *Data, *len) < 0) 
        return FALSE;

    (*Data)[*len] = 0;  // Force a zero marker. Shouldn't be needed, but is 
                        // here to simplify things.

    return TRUE;    
}




LCMSBOOL LCMSEXPORT cmsTakeCalibrationDateTime(struct tm *Dest, cmsHPROFILE hProfile)
{
    LPLCMSICCPROFILE  Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;    
    int n;
    
    n = _cmsSearchTag(Icc, icSigCalibrationDateTimeTag, FALSE);
    if (n < 0) return FALSE;
    
    if (Icc ->TagPtrs[n]) {

        CopyMemory(Dest, Icc ->TagPtrs[n],  sizeof(struct tm));
    }
    else
    {
        icDateTimeNumber timestamp;

        if (Icc -> Seek(Icc, Icc -> TagOffsets[n] + sizeof(icTagBase)))
            return FALSE;
        
        if (Icc ->Read(&timestamp, 1, sizeof(icDateTimeNumber), Icc) != sizeof(icDateTimeNumber))
            return FALSE;
        
        DecodeDateTimeNumber(&timestamp, Dest);
    }

    
    return TRUE;
}



// PSEQ Tag, used in devicelink profiles

LPcmsSEQ LCMSEXPORT cmsReadProfileSequenceDescription(cmsHPROFILE hProfile)
{
    LPLCMSICCPROFILE  Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
    int n;
    icUInt32Number     i, Count;
    icDescStruct       DescStruct;    
    icTagTypeSignature BaseType;
    size_t             size, offset;
    LPcmsSEQ           OutSeq;


    n = _cmsSearchTag(Icc, icSigProfileSequenceDescTag, FALSE);
    if (n < 0) return NULL;                
    
    size   = Icc -> TagSizes[n]; 
    if (size < 12)  return NULL;

    if (Icc -> TagPtrs[n]) {
                                   
            OutSeq = (LPcmsSEQ) _cmsMalloc(size);  
            if (OutSeq == NULL) return NULL;
            CopyMemory(OutSeq, Icc ->TagPtrs[n], size);    
            return OutSeq;
    }

    offset = Icc -> TagOffsets[n];
    
    if (Icc -> Seek(Icc, offset))
            return NULL;

    BaseType = ReadBase(Icc);
    
    if (BaseType != icSigProfileSequenceDescType) return NULL;

    Icc ->Read(&Count, sizeof(icUInt32Number), 1, Icc);
    AdjustEndianess32((LPBYTE) &Count);
    
    size = sizeof(int) + Count * sizeof(cmsPSEQDESC);
    OutSeq = (LPcmsSEQ) _cmsMalloc(size);
    if (OutSeq == NULL) return NULL;

    OutSeq ->n = Count;
    
    // Get structures as well

    for (i=0; i < Count; i++) {
        
        LPcmsPSEQDESC sec = &OutSeq -> seq[i];

        Icc -> Read(&DescStruct, sizeof(icDescStruct) - SIZEOF_UINT8_ALIGNED, 1, Icc);

        AdjustEndianess32((LPBYTE) &DescStruct.deviceMfg);
        AdjustEndianess32((LPBYTE) &DescStruct.deviceModel);
        AdjustEndianess32((LPBYTE) &DescStruct.technology);
        AdjustEndianess32((LPBYTE) &DescStruct.attributes[0]);
        AdjustEndianess32((LPBYTE) &DescStruct.attributes[1]);

        sec ->attributes[0] = DescStruct.attributes[0];
        sec ->attributes[1] = DescStruct.attributes[1];
        sec ->deviceMfg     = DescStruct.deviceMfg;
        sec ->deviceModel   = DescStruct.deviceModel;
        sec ->technology    = DescStruct.technology;

        if (ReadEmbeddedTextTag(Icc, size, sec ->Manufacturer, LCMS_DESC_MAX) < 0) return NULL;
        if (ReadEmbeddedTextTag(Icc, size, sec ->Model, LCMS_DESC_MAX) < 0) return NULL;

    }

    return OutSeq;
}


void LCMSEXPORT cmsFreeProfileSequenceDescription(LPcmsSEQ pseq)
{
    if (pseq) 
        _cmsFree(pseq);
}





// Read a few tags that are hardly required


static
void ReadCriticalTags(LPLCMSICCPROFILE Icc)
{
    cmsHPROFILE hProfile = (cmsHPROFILE) Icc;
     
    if (Icc ->Version >= 0x4000000) {

        // v4 profiles

        MAT3 ChrmCanonical;

         if (ReadICCXYZ(hProfile, 
                      icSigMediaWhitePointTag, 
                      &Icc ->MediaWhitePoint, FALSE) < 0) {

              Icc ->MediaWhitePoint = *cmsD50_XYZ();              
       }

       // Read media black

       if (ReadICCXYZ(hProfile, 
                      icSigMediaBlackPointTag, 
                      &Icc ->MediaBlackPoint, FALSE) < 0) {

              Icc ->MediaBlackPoint.X = 0;
              Icc ->MediaBlackPoint.Y = 0; 
              Icc ->MediaBlackPoint.X = 0;

       }

       NormalizeXYZ(&Icc ->MediaWhitePoint);
       NormalizeXYZ(&Icc ->MediaBlackPoint);

      if (ReadICCXYZArray(hProfile, 
                                icSigChromaticAdaptationTag, 
                                &ChrmCanonical) > 0) {

                MAT3inverse(&ChrmCanonical, &Icc ->ChromaticAdaptation);
        
      }
      else {

                MAT3identity(&Icc ->ChromaticAdaptation);
      }
      

      // Convert media white, black to absolute under original illuminant

      EvalCHRM(&Icc ->MediaWhitePoint, &Icc ->ChromaticAdaptation, &Icc ->MediaWhitePoint);
      EvalCHRM(&Icc ->MediaBlackPoint, &Icc ->ChromaticAdaptation, &Icc ->MediaBlackPoint);
      

    }
    else {

        // v2 profiles   

       // Read media white

       if (ReadICCXYZ(hProfile, 
                      icSigMediaWhitePointTag, 
                      &Icc ->MediaWhitePoint, FALSE) < 0) {

              Icc ->MediaWhitePoint = *cmsD50_XYZ();              
       }

       // Read media black

       if (ReadICCXYZ(hProfile, 
                      icSigMediaBlackPointTag, 
                      &Icc ->MediaBlackPoint, FALSE) < 0) {

              Icc ->MediaBlackPoint.X = 0;
              Icc ->MediaBlackPoint.Y = 0; 
              Icc ->MediaBlackPoint.X = 0;

       }

       NormalizeXYZ(&Icc ->MediaWhitePoint);
       NormalizeXYZ(&Icc ->MediaBlackPoint);


       // Take Bradford as default for Display profiles only. 

       if (cmsGetDeviceClass(hProfile) == icSigDisplayClass) {
           

            cmsAdaptationMatrix(&Icc -> ChromaticAdaptation, 
                                NULL, 
                                &Icc -> Illuminant, 
                                &Icc -> MediaWhitePoint);
       }
       else
            MAT3identity(&Icc ->ChromaticAdaptation);

    }

}


// Create profile from disk file

cmsHPROFILE LCMSEXPORT cmsOpenProfileFromFile(const char *lpFileName, const char *sAccess)
{
       LPLCMSICCPROFILE NewIcc;
       cmsHPROFILE hEmpty;
       

       // Open for write means an empty profile

       if (*sAccess == 'W' || *sAccess == 'w') {

           hEmpty = _cmsCreateProfilePlaceholder();
           NewIcc = (LPLCMSICCPROFILE) (LPSTR) hEmpty;
           NewIcc -> IsWrite = TRUE;
           strncpy(NewIcc ->PhysicalFile, lpFileName, MAX_PATH-1);
           NewIcc ->PhysicalFile[MAX_PATH-1] = 0;

           // Save LUT as 8 bit

           sAccess++;
           if (*sAccess == '8') NewIcc ->SaveAs8Bits = TRUE;

           return hEmpty;
       }


       // Open for read means a file placeholder
      
       NewIcc = _cmsCreateProfileFromFilePlaceholder(lpFileName);
        if (!NewIcc) return NULL;
      
       if (!ReadHeader(NewIcc, FALSE)) return NULL;
                      
       ReadCriticalTags(NewIcc);

       return (cmsHPROFILE) (LPSTR) NewIcc;
}




// Open from memory block

cmsHPROFILE LCMSEXPORT cmsOpenProfileFromMem(LPVOID MemPtr, DWORD dwSize)
{
       LPLCMSICCPROFILE NewIcc;
        
      
       NewIcc = _cmsCreateProfileFromMemPlaceholder(MemPtr, dwSize); 
       if (!NewIcc) return NULL;
       
       if (!ReadHeader(NewIcc, TRUE)) return NULL;
             
       ReadCriticalTags(NewIcc);

       return (cmsHPROFILE) (LPSTR) NewIcc;

}

// Checks a profile for obvious inconsistencies and returns
// TRUE if the profile looks bogus and should probably be
// ignored.
LCMSBOOL LCMSEXPORT cmsProfileIsBogus(cmsHPROFILE hProfile)
{

       cmsCIEXYZTRIPLE primaries;
       VEC3 sum, target, tolerance;
       unsigned i;

       // Read the primaries
       cmsTakeColorants(&primaries, hProfile);

       // Sum the values
       sum.n[0] = primaries.Red.X + primaries.Green.X + primaries.Blue.X;
       sum.n[1] = primaries.Red.Y + primaries.Green.Y + primaries.Blue.Y;
       sum.n[2] = primaries.Red.Z + primaries.Green.Z + primaries.Blue.Z;

       // Build our target vector (see mozilla bug 460629)
       target.n[0] = 0.96420;
       target.n[1] = 1.00000;
       target.n[2] = 0.82491;

       // Our tolerance vector - Recommended by Chris Murphy based on
       // conversion from the LAB space criterion of no more than 3 in any one
       // channel. This is similar to, but slightly more tolerant than Adobe's
       // criterion.
       tolerance.n[0] = 0.02;
       tolerance.n[1] = 0.02;
       tolerance.n[2] = 0.04;

       // Compare with our tolerance
       for (i = 0; i < 3; ++i) {
           if (!(((sum.n[i] - tolerance.n[i]) <= target.n[i]) &&
                 ((sum.n[i] + tolerance.n[i]) >= target.n[i])))
               return TRUE;
       }

       // All Good
       return FALSE;
}

LCMSBOOL LCMSEXPORT cmsCloseProfile(cmsHPROFILE hProfile)
{
       LPLCMSICCPROFILE Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
       LCMSBOOL rc = TRUE;
       icInt32Number i;         

       if (!Icc) return FALSE;

       // Free any precaches
       for (i = 0; i < PRECACHE_TYPE_COUNT; ++i)
              if (Icc->Precache[i] != NULL)
                     PRECACHE_RELEASE(Icc->Precache[i]);

       // Was open in write mode?   
       if (Icc ->IsWrite) {

           Icc ->IsWrite = FALSE;      // Assure no further writting
           rc = _cmsSaveProfile(hProfile, Icc ->PhysicalFile);        
       }
       
       for (i=0; i < Icc -> TagCount; i++) {

                  if (Icc -> TagPtrs[i])
                            free(Icc -> TagPtrs[i]);       
       }

       if (Icc -> stream != NULL) {     // Was a memory (i.e. not serialized) profile?
                 Icc -> Close(Icc);     // No, close the stream      
       }       
            
       free(Icc);   // Free placeholder memory

       return rc;
}



// Write profile ------------------------------------------------------------



static
LCMSBOOL SaveWordsTable(int nEntries, LPWORD Tab, LPLCMSICCPROFILE Icc)
{
   size_t nTabSize = sizeof(WORD) * nEntries;
   LPWORD PtrW = (LPWORD) _cmsMalloc(nTabSize);
   LCMSBOOL rc;

   if (!PtrW) return FALSE;
   CopyMemory(PtrW, Tab, nTabSize);
   AdjustEndianessArray16(PtrW, nEntries);
   rc = Icc ->Write(Icc, nTabSize, PtrW);
   free(PtrW);
   
   return rc;
}



// Saves profile header

static
LCMSBOOL SaveHeader(LPLCMSICCPROFILE Icc)
{
  icHeader Header;
  time_t now = time(NULL);
  
       Header.size        = TransportValue32((icInt32Number) Icc ->UsedSpace);
       Header.cmmId       = TransportValue32(lcmsSignature);
       Header.version     = TransportValue32((icInt32Number) 0x02300000);
       Header.deviceClass = (icProfileClassSignature) TransportValue32(Icc -> DeviceClass);
       Header.colorSpace  = (icColorSpaceSignature) TransportValue32(Icc -> ColorSpace);
       Header.pcs         = (icColorSpaceSignature) TransportValue32(Icc -> PCS);

       //   NOTE: in v4 Timestamp must be in UTC rather than in local time
       EncodeDateTimeNumber(&Header.date, gmtime(&now));

       Header.magic       = TransportValue32(icMagicNumber);

#ifdef NON_WINDOWS
       Header.platform    = (icPlatformSignature)TransportValue32(icSigMacintosh);  
#else
       Header.platform    = (icPlatformSignature)TransportValue32(icSigMicrosoft);  
#endif

       Header.flags        = TransportValue32(Icc -> flags);
       Header.manufacturer = TransportValue32(lcmsSignature);
       Header.model        = TransportValue32(0);
       Header.attributes[0]= TransportValue32(Icc -> attributes);              
       Header.attributes[1]= TransportValue32(0);

       Header.renderingIntent = TransportValue32(Icc -> RenderingIntent);

       // Illuminant is D50

       Header.illuminant.X = TransportValue32(DOUBLE_TO_FIXED(Icc -> Illuminant.X));
       Header.illuminant.Y = TransportValue32(DOUBLE_TO_FIXED(Icc -> Illuminant.Y));
       Header.illuminant.Z = TransportValue32(DOUBLE_TO_FIXED(Icc -> Illuminant.Z));

       Header.creator      = TransportValue32(lcmsSignature);
        
       ZeroMemory(&Header.reserved, sizeof(Header.reserved));

       // Set profile ID
       CopyMemory(Header.reserved, Icc ->ProfileID, 16);
      

       Icc ->UsedSpace = 0; // Mark as begin-of-file

       return Icc ->Write(Icc, sizeof(icHeader), &Header);
}



// Setup base marker

static
LCMSBOOL SetupBase(icTagTypeSignature sig, LPLCMSICCPROFILE Icc)
{
    icTagBase  Base;

    Base.sig = (icTagTypeSignature) TransportValue32(sig);
    ZeroMemory(&Base.reserved, sizeof(Base.reserved));
    return Icc -> Write(Icc, sizeof(icTagBase), &Base);
}


// Store a XYZ tag

static
LCMSBOOL SaveXYZNumber(LPcmsCIEXYZ Value, LPLCMSICCPROFILE Icc)
{

    icXYZNumber XYZ;

    if (!SetupBase(icSigXYZType, Icc)) return FALSE;

    XYZ.X = TransportValue32(DOUBLE_TO_FIXED(Value -> X));
    XYZ.Y = TransportValue32(DOUBLE_TO_FIXED(Value -> Y));
    XYZ.Z = TransportValue32(DOUBLE_TO_FIXED(Value -> Z));


    return Icc -> Write(Icc, sizeof(icXYZNumber), &XYZ);
}


// Store a XYZ array.

static 
LCMSBOOL SaveXYZArray(int n, LPcmsCIEXYZ Value, LPLCMSICCPROFILE Icc)
{
    int i;
    icXYZNumber XYZ;

    if (!SetupBase(icSigS15Fixed16ArrayType, Icc)) return FALSE;

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

        XYZ.X = TransportValue32(DOUBLE_TO_FIXED(Value -> X));
        XYZ.Y = TransportValue32(DOUBLE_TO_FIXED(Value -> Y));
        XYZ.Z = TransportValue32(DOUBLE_TO_FIXED(Value -> Z));

        if (!Icc -> Write(Icc, sizeof(icXYZNumber), &XYZ)) return FALSE;

        Value++;
    }

    return TRUE;
}



// Save a gamma structure as a table

static
LCMSBOOL SaveGammaTable(LPGAMMATABLE Gamma, LPLCMSICCPROFILE Icc)
{
    icInt32Number Count;

        if (!SetupBase(icSigCurveType, Icc)) return FALSE;
          
        Count = TransportValue32(Gamma->nEntries);

        if (!Icc ->Write(Icc, sizeof(icInt32Number), &Count)) return FALSE;

        return SaveWordsTable(Gamma->nEntries, Gamma ->GammaTable, Icc);
}


// Save a gamma structure as a one-value

static
LCMSBOOL SaveGammaOneValue(LPGAMMATABLE Gamma, LPLCMSICCPROFILE Icc)
{	
    icInt32Number Count;
    Fixed32 GammaFixed32;
    WORD    GammaFixed8;
        
        if (!SetupBase(icSigCurveType, Icc)) return FALSE;
                        
        Count = TransportValue32(1);
        if (!Icc ->Write(Icc, sizeof(icInt32Number), &Count)) return FALSE;
        
        GammaFixed32 = DOUBLE_TO_FIXED(Gamma ->Seed.Params[0]);
        GammaFixed8  = (WORD) ((GammaFixed32 >> 8) & 0xFFFF);               
        GammaFixed8  = TransportValue16(GammaFixed8);
        
        return Icc ->Write(Icc, sizeof(icInt16Number), &GammaFixed8);             			    
}

// Save a gamma structure as a parametric gamma

static
LCMSBOOL SaveGammaParametric(LPGAMMATABLE Gamma, LPLCMSICCPROFILE Icc)
{	
    icUInt16Number Type, Reserved;
    int i, nParams;
    int ParamsByType[] = { 1, 3, 4, 5, 7 };

    if (!SetupBase(icSigParametricCurveType, Icc)) return FALSE;
    
    nParams = ParamsByType[Gamma -> Seed.Type];

    Type      = (icUInt16Number) TransportValue16((WORD) Gamma -> Seed. Type);
    Reserved  = (icUInt16Number) TransportValue16((WORD) 0);

    Icc -> Write(Icc, sizeof(icInt16Number),  &Type);   	
    Icc -> Write(Icc, sizeof(icUInt16Number), &Reserved);

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

        icInt32Number val = TransportValue32(DOUBLE_TO_FIXED(Gamma -> Seed.Params[i]));
        Icc ->Write(Icc, sizeof(icInt32Number), &val);             			    
    }

    
    return TRUE;
          
}


// Save a gamma table

static
LCMSBOOL SaveGamma(LPGAMMATABLE Gamma, LPLCMSICCPROFILE Icc)
{
        // Is the gamma curve type supported by ICC format?
        
        if (Gamma -> Seed.Type < 0 || Gamma -> Seed.Type > 5 ||
            
            // has been modified by user?

            _cmsCrc32OfGammaTable(Gamma) != Gamma -> Seed.Crc32) {

            return SaveGammaTable(Gamma, Icc);
        }

        if (Gamma -> Seed.Type == 1) return SaveGammaOneValue(Gamma, Icc);

        // Only v4 profiles are allowed to hold parametric curves

        if (cmsGetProfileICCversion((cmsHPROFILE) Icc) >= 0x4000000)
                return SaveGammaParametric(Gamma, Icc);
        
        // Defaults to save as table

        return SaveGammaTable(Gamma, Icc);

}




// Save an DESC Tag 

static
LCMSBOOL SaveDescription(const char *Text, LPLCMSICCPROFILE Icc)
{

    icUInt32Number len, Count, TotalSize, AlignedSize;
    char Filler[256];

    len = (icUInt32Number) (strlen(Text) + 1);

    // * icInt8Number         desc[count]     * NULL terminated ascii string
    // * icUInt32Number       ucLangCode;     * UniCode language code
    // * icUInt32Number       ucCount;        * UniCode description length
    // * icInt16Number        ucDesc[ucCount];* The UniCode description
    // * icUInt16Number       scCode;         * ScriptCode code
    // * icUInt8Number        scCount;        * ScriptCode count
    // * icInt8Number         scDesc[67];     * ScriptCode Description

    TotalSize = sizeof(icTagBase) + sizeof(icUInt32Number) + len + 
                sizeof(icUInt32Number) + sizeof(icUInt32Number) +
                sizeof(icUInt16Number) + sizeof(icUInt8Number) + 67; 

    AlignedSize = TotalSize;  // Can be unaligned!!
    
    if (!SetupBase(icSigTextDescriptionType, Icc)) return FALSE;
    AlignedSize -= sizeof(icTagBase);

    Count = TransportValue32(len);
    if (!Icc ->Write(Icc, sizeof(icUInt32Number), &Count)) return FALSE;
    AlignedSize -= sizeof(icUInt32Number);

    if (!Icc ->Write(Icc, len, (LPVOID)Text)) return FALSE;
    AlignedSize -= len;

    if (AlignedSize < 0)
            AlignedSize = 0;
    if (AlignedSize > 255) 
            AlignedSize = 255;

    ZeroMemory(Filler, AlignedSize);
    if (!Icc ->Write(Icc, AlignedSize, Filler)) return FALSE;

    return TRUE;
}

// Save an ASCII Tag 

static
LCMSBOOL SaveText(const char *Text, LPLCMSICCPROFILE Icc)
{
    size_t len = strlen(Text) + 1;

    if (!SetupBase(icSigTextType, Icc)) return FALSE;
    if (!Icc ->Write(Icc, len, (LPVOID) Text)) return FALSE;
    return TRUE;
}


// Save one of these new chromaticity values

static
LCMSBOOL SaveOneChromaticity(double x, double y, LPLCMSICCPROFILE Icc)
{
       Fixed32 xf, yf;

       xf = TransportValue32(DOUBLE_TO_FIXED(x));
       yf = TransportValue32(DOUBLE_TO_FIXED(y));

       if (!Icc ->Write(Icc, sizeof(Fixed32), &xf)) return FALSE;
       if (!Icc ->Write(Icc, sizeof(Fixed32), &yf)) return FALSE;

       return TRUE;
}


// New tag added in Addendum II of old spec.

static
LCMSBOOL SaveChromaticities(LPcmsCIExyYTRIPLE chrm, LPLCMSICCPROFILE Icc)
{
       WORD nChans, Table;

       if (!SetupBase(icSigChromaticityType, Icc)) return FALSE;
     
       nChans = TransportValue16(3);
       if (!Icc ->Write(Icc, sizeof(WORD) , &nChans)) return FALSE;
       Table =  TransportValue16(0);
       if (!Icc ->Write(Icc, sizeof(WORD) , &Table)) return FALSE;

       if (!SaveOneChromaticity(chrm -> Red.x, chrm -> Red.y, Icc)) return FALSE;
       if (!SaveOneChromaticity(chrm -> Green.x, chrm -> Green.y, Icc)) return FALSE;
       if (!SaveOneChromaticity(chrm -> Blue.x, chrm -> Blue.y, Icc)) return FALSE;

       return TRUE;
}


static
LCMSBOOL SaveSequenceDescriptionTag(LPcmsSEQ seq, LPLCMSICCPROFILE Icc)
{
    icUInt32Number nSeqs;
    icDescStruct   DescStruct;
    int i, n = seq ->n;
    LPcmsPSEQDESC pseq = seq ->seq;

    if (!SetupBase(icSigProfileSequenceDescType, Icc)) return FALSE;
    
    nSeqs = TransportValue32(n);

    if (!Icc ->Write(Icc, sizeof(icUInt32Number) , &nSeqs)) return FALSE;

    for (i=0; i < n; i++) {
        
        LPcmsPSEQDESC sec = pseq + i;

       
        DescStruct.deviceMfg    = (icTagTypeSignature) TransportValue32(sec ->deviceMfg);
        DescStruct.deviceModel  = (icTagTypeSignature) TransportValue32(sec ->deviceModel);
        DescStruct.technology   = (icTechnologySignature) TransportValue32(sec ->technology);
        DescStruct.attributes[0]= TransportValue32(sec ->attributes[0]);
        DescStruct.attributes[1]= TransportValue32(sec ->attributes[1]);

        if (!Icc ->Write(Icc, sizeof(icDescStruct) - SIZEOF_UINT8_ALIGNED, &DescStruct)) return FALSE;
        
        if (!SaveDescription(sec ->Manufacturer, Icc)) return FALSE;
        if (!SaveDescription(sec ->Model, Icc)) return FALSE;        
    }

    return TRUE;
}


// Saves a timestamp tag

static
LCMSBOOL SaveDateTimeNumber(const struct tm *DateTime, LPLCMSICCPROFILE Icc)
{
    icDateTimeNumber Dest;

    if (!SetupBase(icSigDateTimeType, Icc)) return FALSE;
    EncodeDateTimeNumber(&Dest, DateTime);
    if (!Icc ->Write(Icc, sizeof(icDateTimeNumber), &Dest)) return FALSE;

    return TRUE;
}


// Saves a named color list into a named color profile
static
LCMSBOOL SaveNamedColorList(LPcmsNAMEDCOLORLIST NamedColorList, LPLCMSICCPROFILE Icc)
{

    icUInt32Number      vendorFlag;     // Bottom 16 bits for IC use 
    icUInt32Number      count;          // Count of named colors 
    icUInt32Number      nDeviceCoords;  // Num of device coordinates 
    char                prefix[32];     // Prefix for each color name 
    char                suffix[32];     // Suffix for each color name 
    int i;

    if (!SetupBase(icSigNamedColor2Type, Icc)) return FALSE;

    vendorFlag    = TransportValue32(0);
    count         = TransportValue32(NamedColorList ->nColors);
    nDeviceCoords = TransportValue32(NamedColorList ->ColorantCount);

    strncpy(prefix, (const char*) NamedColorList->Prefix, 32);
    strncpy(suffix, (const char*) NamedColorList->Suffix, 32);
                  
    suffix[31] = prefix[31] = 0;

    if (!Icc ->Write(Icc, sizeof(icUInt32Number), &vendorFlag)) return FALSE;
    if (!Icc ->Write(Icc, sizeof(icUInt32Number), &count)) return FALSE;
    if (!Icc ->Write(Icc, sizeof(icUInt32Number), &nDeviceCoords)) return FALSE;
    if (!Icc ->Write(Icc, 32 , prefix)) return FALSE;
    if (!Icc ->Write(Icc, 32 , suffix)) return FALSE;

    for (i=0; i < NamedColorList ->nColors; i++) {

          icUInt16Number  PCS[3];
          icUInt16Number  Colorant[MAXCHANNELS];
          char            root[32];
          LPcmsNAMEDCOLOR Color;
          int j;

                    Color = NamedColorList ->List + i;

                    strncpy(root, Color ->Name, 32);
                    Color ->Name[32] = 0;

                    if (!Icc ->Write(Icc, 32 , root)) return FALSE;
                    
                    for (j=0; j < 3; j++)
                        PCS[j] = TransportValue16(Color ->PCS[j]);

                    if (!Icc ->Write(Icc, 3 * sizeof(icUInt16Number), PCS)) return FALSE;

                    for (j=0; j < NamedColorList ->ColorantCount; j++)
                        Colorant[j] = TransportValue16(Color ->DeviceColorant[j]);
                        
                    if (!Icc ->Write(Icc, 
                            NamedColorList ->ColorantCount * sizeof(icUInt16Number), Colorant)) return FALSE;                                                           
    }


    return TRUE;
}



// Saves a colorant table. It is using the named color structure for simplicity sake

static
LCMSBOOL SaveColorantTable(LPcmsNAMEDCOLORLIST NamedColorList, LPLCMSICCPROFILE Icc)
{
     icUInt32Number count;  // Count of named colors 
     int i;

     if (!SetupBase(icSigColorantTableType, Icc)) return FALSE;

     count = TransportValue32(NamedColorList ->nColors);

     if (!Icc ->Write(Icc, sizeof(icUInt32Number), &count)) return FALSE;

     for (i=0; i < NamedColorList ->nColors; i++) {

      icUInt16Number PCS[3];          
      icInt8Number root[33];
      LPcmsNAMEDCOLOR Color;
      int j;

            Color = NamedColorList ->List + i;

            strncpy((char*) root, Color ->Name, 32);
            root[32] = 0;

            if (!Icc ->Write(Icc, 32 , root)) return FALSE;
            
            for (j=0; j < 3; j++)
                PCS[j] = TransportValue16(Color ->PCS[j]);

            if (!Icc ->Write(Icc, 3 * sizeof(icUInt16Number), PCS)) return FALSE;

     }


     return TRUE;
}

// Does serialization of LUT16 and writes it. 

static
LCMSBOOL SaveLUT(const LUT* NewLUT, LPLCMSICCPROFILE Icc)
{
       icLut16 LUT16;
       unsigned int i;
       size_t nTabSize;
       WORD NullTbl[2] = { 0, 0xFFFFU};


       if (!SetupBase(icSigLut16Type, Icc)) return FALSE;

       LUT16.clutPoints = (icUInt8Number) NewLUT -> cLutPoints;
       LUT16.inputChan  = (icUInt8Number) NewLUT -> InputChan;
       LUT16.outputChan = (icUInt8Number) NewLUT -> OutputChan;

       LUT16.inputEnt   = TransportValue16((WORD) ((NewLUT -> wFlags & LUT_HASTL1) ? NewLUT -> InputEntries  : 2));
       LUT16.outputEnt  = TransportValue16((WORD) ((NewLUT -> wFlags & LUT_HASTL2) ? NewLUT -> OutputEntries : 2));

       if (NewLUT -> wFlags & LUT_HASMATRIX) {

           LUT16.e00 = TransportValue32(NewLUT -> Matrix.v[0].n[0]);
           LUT16.e01 = TransportValue32(NewLUT -> Matrix.v[0].n[1]);
           LUT16.e02 = TransportValue32(NewLUT -> Matrix.v[0].n[2]);
           LUT16.e10 = TransportValue32(NewLUT -> Matrix.v[1].n[0]);
           LUT16.e11 = TransportValue32(NewLUT -> Matrix.v[1].n[1]);
           LUT16.e12 = TransportValue32(NewLUT -> Matrix.v[1].n[2]);
           LUT16.e20 = TransportValue32(NewLUT -> Matrix.v[2].n[0]);
           LUT16.e21 = TransportValue32(NewLUT -> Matrix.v[2].n[1]);
           LUT16.e22 = TransportValue32(NewLUT -> Matrix.v[2].n[2]);
           }
       else {

           LUT16.e00 = TransportValue32(DOUBLE_TO_FIXED(1));
           LUT16.e01 = TransportValue32(DOUBLE_TO_FIXED(0));
           LUT16.e02 = TransportValue32(DOUBLE_TO_FIXED(0));
           LUT16.e10 = TransportValue32(DOUBLE_TO_FIXED(0));
           LUT16.e11 = TransportValue32(DOUBLE_TO_FIXED(1));
           LUT16.e12 = TransportValue32(DOUBLE_TO_FIXED(0));
           LUT16.e20 = TransportValue32(DOUBLE_TO_FIXED(0));
           LUT16.e21 = TransportValue32(DOUBLE_TO_FIXED(0));
           LUT16.e22 = TransportValue32(DOUBLE_TO_FIXED(1));
       }


       // Save header

       Icc -> Write(Icc,  sizeof(icLut16)- SIZEOF_UINT16_ALIGNED, &LUT16);

       // The prelinearization table

       for (i=0; i < NewLUT -> InputChan; i++) {

        if (NewLUT -> wFlags & LUT_HASTL1) {

               if (!SaveWordsTable(NewLUT -> InputEntries, 
                                   NewLUT -> L1[i], Icc)) return FALSE;

        }
        else Icc -> Write(Icc, sizeof(WORD)* 2, NullTbl);
       }


       nTabSize = (NewLUT -> OutputChan * uipow(NewLUT->cLutPoints,
                                                 NewLUT->InputChan));
       // The 3D CLUT.

       if (nTabSize > 0) {

             if (!SaveWordsTable((int) nTabSize, NewLUT -> T, Icc)) return FALSE;
       }
       // The postlinearization table

       for (i=0; i < NewLUT -> OutputChan; i++) {

        if (NewLUT -> wFlags & LUT_HASTL2) {

                if (!SaveWordsTable(NewLUT -> OutputEntries, 
                                    NewLUT -> L2[i], Icc)) return FALSE;
        }
        else Icc -> Write(Icc, sizeof(WORD)* 2, NullTbl);

       }

        return TRUE;
}



// Does serialization of LUT8 and writes it

static
LCMSBOOL SaveLUT8(const LUT* NewLUT, LPLCMSICCPROFILE Icc)
{
       icLut8 LUT8;
       unsigned int i, j;
       size_t nTabSize;
       BYTE val;

       // Sanity check
       
       if (NewLUT -> wFlags & LUT_HASTL1) {

           if (NewLUT -> InputEntries != 256) {
                cmsSignalError(LCMS_ERRC_ABORTED, "LUT8 needs 256 entries on prelinearization");
                return FALSE;
           }

       }


       if (NewLUT -> wFlags & LUT_HASTL2) {

           if (NewLUT -> OutputEntries != 256) {
                cmsSignalError(LCMS_ERRC_ABORTED, "LUT8 needs 256 entries on postlinearization");
                return FALSE;
           }
       }

       

       if (!SetupBase(icSigLut8Type, Icc)) return FALSE;

       LUT8.clutPoints = (icUInt8Number) NewLUT -> cLutPoints;
       LUT8.inputChan  = (icUInt8Number) NewLUT -> InputChan;
       LUT8.outputChan = (icUInt8Number) NewLUT -> OutputChan;
       

       if (NewLUT -> wFlags & LUT_HASMATRIX) {

       LUT8.e00 = TransportValue32(NewLUT -> Matrix.v[0].n[0]);
       LUT8.e01 = TransportValue32(NewLUT -> Matrix.v[0].n[1]);
       LUT8.e02 = TransportValue32(NewLUT -> Matrix.v[0].n[2]);
       LUT8.e10 = TransportValue32(NewLUT -> Matrix.v[1].n[0]);
       LUT8.e11 = TransportValue32(NewLUT -> Matrix.v[1].n[1]);
       LUT8.e12 = TransportValue32(NewLUT -> Matrix.v[1].n[2]);
       LUT8.e20 = TransportValue32(NewLUT -> Matrix.v[2].n[0]);
       LUT8.e21 = TransportValue32(NewLUT -> Matrix.v[2].n[1]);
       LUT8.e22 = TransportValue32(NewLUT -> Matrix.v[2].n[2]);
       }
       else {

       LUT8.e00 = TransportValue32(DOUBLE_TO_FIXED(1));
       LUT8.e01 = TransportValue32(DOUBLE_TO_FIXED(0));
       LUT8.e02 = TransportValue32(DOUBLE_TO_FIXED(0));
       LUT8.e10 = TransportValue32(DOUBLE_TO_FIXED(0));
       LUT8.e11 = TransportValue32(DOUBLE_TO_FIXED(1));
       LUT8.e12 = TransportValue32(DOUBLE_TO_FIXED(0));
       LUT8.e20 = TransportValue32(DOUBLE_TO_FIXED(0));
       LUT8.e21 = TransportValue32(DOUBLE_TO_FIXED(0));
       LUT8.e22 = TransportValue32(DOUBLE_TO_FIXED(1));
       }


       // Save header

       Icc -> Write(Icc,  sizeof(icLut8)- SIZEOF_UINT8_ALIGNED, &LUT8);

       // The prelinearization table

       for (i=0; i < NewLUT -> InputChan; i++) {

           for (j=0; j < 256; j++) {

               if (NewLUT -> wFlags & LUT_HASTL1)
                        val = (BYTE) floor(NewLUT ->L1[i][j] / 257.0 + .5);
               else
                        val = (BYTE) j;

               Icc ->Write(Icc, 1, &val);
           }
               
       }


       nTabSize = (NewLUT -> OutputChan * uipow(NewLUT->cLutPoints,
                                                 NewLUT->InputChan));
       // The 3D CLUT.

       for (j=0; j < nTabSize; j++) {

              val = (BYTE) floor(NewLUT ->T[j] / 257.0 + .5);
              Icc ->Write(Icc, 1, &val);
       }

       // The postlinearization table

       for (i=0; i < NewLUT -> OutputChan; i++) {

           for (j=0; j < 256; j++) {

               if (NewLUT -> wFlags & LUT_HASTL2)
                    val = (BYTE) floor(NewLUT ->L2[i][j] / 257.0 + .5);
               else
                    val = (BYTE) j;

               Icc ->Write(Icc, 1, &val);
           }

       }

        return TRUE;
}



// Set the LUT bitdepth to be saved

void LCMSEXPORT _cmsSetLUTdepth(cmsHPROFILE hProfile, int depth)
{
    LPLCMSICCPROFILE Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;

    switch (depth) {

    case 8:  Icc ->SaveAs8Bits = TRUE; break;
    case 16: Icc ->SaveAs8Bits = FALSE; break;      
        
    default:
        cmsSignalError(LCMS_ERRC_ABORTED, "%d is an unsupported as bitdepth, use 8 or 16 only.", depth);
    }
}


// Saves Tag directory

static
LCMSBOOL SaveTagDirectory(LPLCMSICCPROFILE Icc)
{
       icInt32Number i;
       icTag Tag;       
       icInt32Number Count = 0;

       // Get true count
       for (i=0;  i < Icc -> TagCount; i++) {
            if (Icc ->TagNames[i] != 0)
                    Count++;
       }

       Count = TransportValue32(Count);
       if (!Icc ->Write(Icc, sizeof(icInt32Number) , &Count)) return FALSE;

       for (i=0; i < Icc -> TagCount; i++) {

          if (Icc ->TagNames[i] == 0) continue;

          Tag.sig    = (icTagSignature)TransportValue32(Icc -> TagNames[i]);
          Tag.offset = TransportValue32((icInt32Number) Icc -> TagOffsets[i]);
          Tag.size   = TransportValue32((icInt32Number) Icc -> TagSizes[i]);

          if (!Icc ->Write(Icc, sizeof(icTag), &Tag)) return FALSE;
       }

       return TRUE;
}


// Dump tag contents

static
LCMSBOOL SaveTags(LPLCMSICCPROFILE Icc, LPLCMSICCPROFILE FileOrig)
{

    LPBYTE Data;
    icInt32Number i;
    size_t Begin;
    size_t AlignedSpace, FillerSize;


    for (i=0; i < Icc -> TagCount; i++) {

         if (Icc ->TagNames[i] == 0) continue;
        
        // Align to DWORD boundary, following new spec.
        
        AlignedSpace = ALIGNLONG(Icc ->UsedSpace);
        FillerSize  = AlignedSpace - Icc ->UsedSpace;
        if (FillerSize > 0)  {
            
            BYTE Filler[20];

            ZeroMemory(Filler, 16);
            if (!Icc ->Write(Icc, FillerSize, Filler)) return FALSE;
        }
        
        
       Icc -> TagOffsets[i] = Begin = Icc ->UsedSpace;
       Data = (LPBYTE) Icc -> TagPtrs[i];
	   if (!Data) {

		   // Reach here if we are copying a tag from a disk-based ICC profile which has not been modified by user. 
		   // In this case a blind copy of the block data is performed

		   if (Icc -> TagOffsets[i]) {

					size_t TagSize   = FileOrig -> TagSizes[i];
					size_t TagOffset = FileOrig -> TagOffsets[i];
					void* Mem;

					if (FileOrig ->Seek(FileOrig, TagOffset)) return FALSE;

					Mem = _cmsMalloc(TagSize);					

					if (FileOrig ->Read(Mem, TagSize, 1, FileOrig) != 1) return FALSE;
					if (!Icc ->Write(Icc, TagSize, Mem)) return FALSE;

					Icc -> TagSizes[i] = (Icc ->UsedSpace - Begin);
					free(Mem);
		   }

              continue;
	   }


       switch (Icc -> TagNames[i]) {

       case icSigProfileDescriptionTag: 
       case icSigDeviceMfgDescTag:
       case icSigDeviceModelDescTag:
              if (!SaveDescription((const char *) Data, Icc)) return FALSE;
              break;

       case icSigRedColorantTag:
       case icSigGreenColorantTag:
       case icSigBlueColorantTag:
       case icSigMediaWhitePointTag:
       case icSigMediaBlackPointTag:           
               if (!SaveXYZNumber((LPcmsCIEXYZ) Data, Icc)) return FALSE;
               break;


       case icSigRedTRCTag:
       case icSigGreenTRCTag:
       case icSigBlueTRCTag:
       case icSigGrayTRCTag:
               if (!SaveGamma((LPGAMMATABLE) Data, Icc)) return FALSE;
               break;

       case icSigCharTargetTag:
       case icSigCopyrightTag:      
              if (!SaveText((const char *) Data, Icc)) return FALSE;
              break;

       case icSigChromaticityTag:
              if (!SaveChromaticities((LPcmsCIExyYTRIPLE) Data, Icc)) return FALSE;
              break;

       // Save LUT 

       case icSigAToB0Tag:
       case icSigAToB1Tag:
       case icSigAToB2Tag:
       case icSigBToA0Tag:
       case icSigBToA1Tag:
       case icSigBToA2Tag:
       case icSigGamutTag:
       case icSigPreview0Tag:
       case icSigPreview1Tag:
       case icSigPreview2Tag:

                if (Icc ->SaveAs8Bits) {

                        if (!SaveLUT8((LPLUT) Data, Icc)) return FALSE;
                }
                else {

                        if (!SaveLUT((LPLUT) Data, Icc)) return FALSE;
                }
                break;

       case icSigProfileSequenceDescTag:               
              if (!SaveSequenceDescriptionTag((LPcmsSEQ) Data, Icc)) return FALSE;              
              break;


       case icSigNamedColor2Tag:
             if (!SaveNamedColorList((LPcmsNAMEDCOLORLIST) Data, Icc)) return FALSE;              
             break;


       case icSigCalibrationDateTimeTag:
             if (!SaveDateTimeNumber((struct tm *) Data, Icc)) return FALSE;              
             break;


       case icSigColorantTableTag:
       case icSigColorantTableOutTag:
             if (!SaveColorantTable((LPcmsNAMEDCOLORLIST) Data, Icc)) return FALSE;    
             break;


       case icSigChromaticAdaptationTag:
              if (!SaveXYZArray(3, (LPcmsCIEXYZ) Data, Icc)) return FALSE;
              break;

       default:
              return FALSE;
       }

       Icc -> TagSizes[i] = (Icc ->UsedSpace - Begin);
       }

        

       return TRUE;
}



// Add tags to profile structure

LCMSBOOL LCMSEXPORT cmsAddTag(cmsHPROFILE hProfile, icTagSignature sig, const void* Tag)
{
   LCMSBOOL rc;

   switch (sig) {

       case icSigCharTargetTag:
       case icSigCopyrightTag:             
       case icSigProfileDescriptionTag: 
       case icSigDeviceMfgDescTag:
       case icSigDeviceModelDescTag:
              rc = _cmsAddTextTag(hProfile, sig, (const char*) Tag);
              break;

       case icSigRedColorantTag:
       case icSigGreenColorantTag:
       case icSigBlueColorantTag:
       case icSigMediaWhitePointTag:
       case icSigMediaBlackPointTag:           
              rc = _cmsAddXYZTag(hProfile, sig, (const cmsCIEXYZ*) Tag);
              break; 

       case icSigRedTRCTag:
       case icSigGreenTRCTag:
       case icSigBlueTRCTag:
       case icSigGrayTRCTag:
              rc =  _cmsAddGammaTag(hProfile, sig, (LPGAMMATABLE) Tag);
              break;
                     
       case icSigAToB0Tag:
       case icSigAToB1Tag:
       case icSigAToB2Tag:
       case icSigBToA0Tag:
       case icSigBToA1Tag:
       case icSigBToA2Tag:
       case icSigGamutTag:
       case icSigPreview0Tag:
       case icSigPreview1Tag:
       case icSigPreview2Tag:
              rc =  _cmsAddLUTTag(hProfile, sig, Tag);
              break;

       case icSigChromaticityTag:
              rc =  _cmsAddChromaticityTag(hProfile, sig, (LPcmsCIExyYTRIPLE) Tag);              
              break;
        
       case icSigProfileSequenceDescTag:
              rc = _cmsAddSequenceDescriptionTag(hProfile, sig, (LPcmsSEQ) Tag);
              break;

       case icSigNamedColor2Tag:
              rc = _cmsAddNamedColorTag(hProfile, sig, (LPcmsNAMEDCOLORLIST) Tag);
             break;

       case icSigCalibrationDateTimeTag:
              rc = _cmsAddDateTimeTag(hProfile, sig, (struct tm*) Tag);
              break;
         
       case icSigColorantTableTag:
       case icSigColorantTableOutTag:
              rc = _cmsAddColorantTableTag(hProfile, sig, (LPcmsNAMEDCOLORLIST) Tag);
              break;


       case icSigChromaticAdaptationTag:
              rc = _cmsAddChromaticAdaptationTag(hProfile, sig, (const cmsCIEXYZ*) Tag);
              break;

       default:
            cmsSignalError(LCMS_ERRC_ABORTED, "cmsAddTag: Tag '%x' is unsupported", sig);
            return FALSE;
   }

   // Check for critical tags

   switch (sig) {

   case icSigMediaWhitePointTag:
   case icSigMediaBlackPointTag:
   case icSigChromaticAdaptationTag:
       
        ReadCriticalTags((LPLCMSICCPROFILE) hProfile);
        break;

   default:;
   }

   return rc;

}

// Low-level save to disk. It closes the profile on exit

LCMSBOOL LCMSEXPORT _cmsSaveProfile(cmsHPROFILE hProfile, const char* FileName)
{       
       LPLCMSICCPROFILE Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
       LCMSICCPROFILE Keep;
       LCMSBOOL rc;

        CopyMemory(&Keep, Icc, sizeof(LCMSICCPROFILE));
       _cmsSetSaveToDisk(Icc, NULL);    
             
       // Pass #1 does compute offsets
     
       if (!SaveHeader(Icc)) return FALSE;
       if (!SaveTagDirectory(Icc)) return FALSE;
       if (!SaveTags(Icc, &Keep)) return FALSE;


       _cmsSetSaveToDisk(Icc, FileName);    


       // Pass #2 does save to file
     
       if (!SaveHeader(Icc)) goto CleanUp;
       if (!SaveTagDirectory(Icc)) goto CleanUp;
       if (!SaveTags(Icc, &Keep)) goto CleanUp;
      
       rc = (Icc ->Close(Icc) == 0);
       CopyMemory(Icc, &Keep, sizeof(LCMSICCPROFILE));
       return rc;
       

   CleanUp:
     
       Icc ->Close(Icc);
       unlink(FileName);
       CopyMemory(Icc, &Keep, sizeof(LCMSICCPROFILE));
       return FALSE;
}


// Low-level save from open stream
LCMSBOOL LCMSEXPORT _cmsSaveProfileToMem(cmsHPROFILE hProfile, void *MemPtr, 
                                                           size_t* BytesNeeded)
{
    LPLCMSICCPROFILE Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;     
    LCMSICCPROFILE Keep;
    
    
    CopyMemory(&Keep, Icc, sizeof(LCMSICCPROFILE));
    
    _cmsSetSaveToMemory(Icc, NULL, 0);
    
    // Pass #1 does compute offsets
    
    if (!SaveHeader(Icc)) return FALSE;
    if (!SaveTagDirectory(Icc)) return FALSE;
    if (!SaveTags(Icc, &Keep)) return FALSE;              
    
    if (!MemPtr) {
        
        // update BytesSaved so caller knows how many bytes are needed for MemPtr
        *BytesNeeded = Icc ->UsedSpace;
        CopyMemory(Icc, &Keep, sizeof(LCMSICCPROFILE));
        return TRUE;
    }        
    
    if (*BytesNeeded < Icc ->UsedSpace) {

        // need at least UsedSpace in MemPtr to continue       
        CopyMemory(Icc, &Keep, sizeof(LCMSICCPROFILE));
        return FALSE;
    }
    
    _cmsSetSaveToMemory(Icc, MemPtr, *BytesNeeded);
       
       
    // Pass #2 does save to file into supplied stream     
    if (!SaveHeader(Icc)) goto CleanUp;
    if (!SaveTagDirectory(Icc)) goto CleanUp;
    if (!SaveTags(Icc, &Keep)) goto CleanUp;
       
    // update BytesSaved so caller knows how many bytes put into stream
    *BytesNeeded = Icc ->UsedSpace;
       
    Icc ->Close(Icc);
    CopyMemory(Icc, &Keep, sizeof(LCMSICCPROFILE));
    return TRUE;
       
CleanUp:
       
    Icc ->Close(Icc);
    CopyMemory(Icc, &Keep, sizeof(LCMSICCPROFILE));
    return FALSE;
}

