// // 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 > 5) { 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 > 5) { 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) * sizeof(wchar_t)); 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(×tamp, 1, sizeof(icDateTimeNumber), Icc) != sizeof(icDateTimeNumber)) return FALSE; DecodeDateTimeNumber(×tamp, 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; } LCMSBOOL LCMSEXPORT cmsCloseProfile(cmsHPROFILE hProfile) { LPLCMSICCPROFILE Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile; LCMSBOOL rc = TRUE; icInt32Number i; if (!Icc) return FALSE; // 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; }