// Chromatic adaptation matrix. Fix some issues as well cmsBool _cmsReadCHAD(cmsMAT3* Dest, cmsHPROFILE hProfile) { cmsMAT3* Tag; _cmsAssert(Dest != NULL); Tag = (cmsMAT3*) cmsReadTag(hProfile, cmsSigChromaticAdaptationTag); if (Tag != NULL) { *Dest = *Tag; return TRUE; } // No CHAD available, default it to identity _cmsMAT3identity(Dest); // V2 display profiles should give D50 if (cmsGetEncodedICCversion(hProfile) < 0x4000000) { if (cmsGetDeviceClass(hProfile) == cmsSigDisplayClass) { cmsCIEXYZ* White = (cmsCIEXYZ*) cmsReadTag(hProfile, cmsSigMediaWhitePointTag); if (White == NULL) { _cmsMAT3identity(Dest); return TRUE; } return _cmsAdaptationMatrix(Dest, NULL, White, cmsD50_XYZ()); } } return TRUE; }
cmsBool CMSEXPORT _cmsMAT3isIdentity(const cmsMAT3* a) { cmsMAT3 Identity; int i, j; _cmsMAT3identity(&Identity); for (i=0; i < 3; i++) for (j=0; j < 3; j++) if (!CloseEnough(a ->v[i].n[j], Identity.v[i].n[j])) return FALSE; return TRUE; }
// Just to see if m matrix should be applied static cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off) { cmsFloat64Number diff = 0; cmsMAT3 Ident; int i; if (m == NULL && off == NULL) return TRUE; // NULL is allowed as an empty layer if (m == NULL && off != NULL) return FALSE; // This is an internal error _cmsMAT3identity(&Ident); for (i=0; i < 3*3; i++) diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]); for (i=0; i < 3; i++) diff += fabs(((cmsFloat64Number*)off)[i]); return (diff < 0.002); }
// Default handler for ICC-style intents static cmsPipeline* DefaultICCintents(cmsContext ContextID, cmsUInt32Number nProfiles, cmsUInt32Number TheIntents[], cmsHPROFILE hProfiles[], cmsBool BPC[], cmsFloat64Number AdaptationStates[], cmsUInt32Number dwFlags) { cmsPipeline* Lut = NULL; cmsPipeline* Result; cmsHPROFILE hProfile; cmsMAT3 m; cmsVEC3 off; cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace; cmsProfileClassSignature ClassSig; cmsUInt32Number i, Intent; // For safety if (nProfiles == 0) return NULL; // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined' Result = cmsPipelineAlloc(ContextID, 0, 0); if (Result == NULL) return NULL; CurrentColorSpace = cmsGetColorSpace(hProfiles[0]); for (i=0; i < nProfiles; i++) { cmsBool lIsDeviceLink, lIsInput; hProfile = hProfiles[i]; ClassSig = cmsGetDeviceClass(hProfile); lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass ); // First profile is used as input unless devicelink or abstract if ((i == 0) && !lIsDeviceLink) { lIsInput = TRUE; } else { // Else use profile in the input direction if current space is not PCS lIsInput = (CurrentColorSpace != cmsSigXYZData) && (CurrentColorSpace != cmsSigLabData); } Intent = TheIntents[i]; if (lIsInput || lIsDeviceLink) { ColorSpaceIn = cmsGetColorSpace(hProfile); ColorSpaceOut = cmsGetPCS(hProfile); } else { ColorSpaceIn = cmsGetPCS(hProfile); ColorSpaceOut = cmsGetColorSpace(hProfile); } if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) { cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch"); goto Error; } // If devicelink is found, then no custom intent is allowed and we can // read the LUT to be applied. Settings don't apply here. if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) { // Get the involved LUT from the profile Lut = _cmsReadDevicelinkLUT(hProfile, Intent); if (Lut == NULL) goto Error; // What about abstract profiles? if (ClassSig == cmsSigAbstractClass && i > 0) { if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error; } else { _cmsMAT3identity(&m); _cmsVEC3init(&off, 0, 0, 0); } if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error; } else { if (lIsInput) { // Input direction means non-pcs connection, so proceed like devicelinks Lut = _cmsReadInputLUT(hProfile, Intent); if (Lut == NULL) goto Error; } else { // Output direction means PCS connection. Intent may apply here Lut = _cmsReadOutputLUT(hProfile, Intent); if (Lut == NULL) goto Error; if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error; if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error; } } // Concatenate to the output LUT if (!cmsPipelineCat(Result, Lut)) goto Error; cmsPipelineFree(Lut); Lut = NULL; // Update current space CurrentColorSpace = ColorSpaceOut; } return Result; Error: if (Lut != NULL) cmsPipelineFree(Lut); if (Result != NULL) cmsPipelineFree(Result); return NULL; cmsUNUSED_PARAMETER(dwFlags); }
// Compute the conversion layer static cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[], cmsUInt32Number Intent, cmsBool BPC, cmsFloat64Number AdaptationState, cmsMAT3* m, cmsVEC3* off) { int k; // m and off are set to identity and this is detected latter on _cmsMAT3identity(m); _cmsVEC3init(off, 0, 0, 0); // If intent is abs. colorimetric, if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) { cmsCIEXYZ WhitePointIn, WhitePointOut; cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut; _cmsReadMediaWhitePoint(&WhitePointIn, hProfiles[i-1]); _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]); _cmsReadMediaWhitePoint(&WhitePointOut, hProfiles[i]); _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]); if (!ComputeAbsoluteIntent(AdaptationState, &WhitePointIn, &ChromaticAdaptationMatrixIn, &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE; } else { // Rest of intents may apply BPC. if (BPC) { cmsCIEXYZ BlackPointIn, BlackPointOut; cmsDetectBlackPoint(&BlackPointIn, hProfiles[i-1], Intent, 0); cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0); // If black points are equal, then do nothing if (BlackPointIn.X != BlackPointOut.X || BlackPointIn.Y != BlackPointOut.Y || BlackPointIn.Z != BlackPointOut.Z) ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off); } } // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0, // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so // we have first to convert from encoded to XYZ and then convert back to encoded. // y = Mx + Off // x = x'c // y = M x'c + Off // y = y'c; y' = y / c // y' = (Mx'c + Off) /c = Mx' + (Off / c) for (k=0; k < 3; k++) { off ->n[k] /= MAX_ENCODEABLE_XYZ; } return TRUE; }
// Join scalings to obtain relative input to absolute and then to relative output. // Result is stored in a 3x3 matrix static cmsBool ComputeAbsoluteIntent(cmsFloat64Number AdaptationState, const cmsCIEXYZ* WhitePointIn, const cmsMAT3* ChromaticAdaptationMatrixIn, const cmsCIEXYZ* WhitePointOut, const cmsMAT3* ChromaticAdaptationMatrixOut, cmsMAT3* m) { cmsMAT3 Scale, m1, m2, m3, m4; // Adaptation state if (AdaptationState == 1.0) { // Observer is fully adapted. Keep chromatic adaptation. // That is the standard V4 behaviour _cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0); _cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0); _cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z); } else { // Incomplete adaptation. This is an advanced feature. _cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0); _cmsVEC3init(&Scale.v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0); _cmsVEC3init(&Scale.v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z); if (AdaptationState == 0.0) { m1 = *ChromaticAdaptationMatrixOut; _cmsMAT3per(&m2, &m1, &Scale); // m2 holds CHAD from output white to D50 times abs. col. scaling // Observer is not adapted, undo the chromatic adaptation _cmsMAT3per(m, &m2, ChromaticAdaptationMatrixOut); m3 = *ChromaticAdaptationMatrixIn; if (!_cmsMAT3inverse(&m3, &m4)) return FALSE; _cmsMAT3per(m, &m2, &m4); } else { cmsMAT3 MixedCHAD; cmsFloat64Number TempSrc, TempDest, Temp; m1 = *ChromaticAdaptationMatrixIn; if (!_cmsMAT3inverse(&m1, &m2)) return FALSE; _cmsMAT3per(&m3, &m2, &Scale); // m3 holds CHAD from input white to D50 times abs. col. scaling TempSrc = CHAD2Temp(ChromaticAdaptationMatrixIn); TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut); if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) { _cmsMAT3identity(m); return TRUE; } Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc; // Get a CHAD from whatever output temperature to D50. This replaces output CHAD Temp2CHAD(&MixedCHAD, Temp); _cmsMAT3per(m, &m3, &MixedCHAD); } } return TRUE; }