// Approximate a blackbody illuminant based on CHAD information static cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad) { // Convert D50 across inverse CHAD to get the absolute white point cmsVEC3 d, s; cmsCIEXYZ Dest; cmsCIExyY DestChromaticity; cmsFloat64Number TempK; cmsMAT3 m1, m2; m1 = *Chad; if (!_cmsMAT3inverse(&m1, &m2)) return FALSE; s.n[VX] = cmsD50_XYZ() -> X; s.n[VY] = cmsD50_XYZ() -> Y; s.n[VZ] = cmsD50_XYZ() -> Z; _cmsMAT3eval(&d, &m2, &s); Dest.X = d.n[VX]; Dest.Y = d.n[VY]; Dest.Z = d.n[VZ]; cmsXYZ2xyY(&DestChromaticity, &Dest); if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity)) return -1.0; return TempK; }
static cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile) { cmsPipeline* Lut; cmsToneCurve *Shapes[3], *InvShapes[3]; cmsMAT3 Mat, Inv; int i, j; cmsContext ContextID = cmsGetProfileContextID(hProfile); if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile)) return NULL; if (!_cmsMAT3inverse(&Mat, &Inv)) return NULL; // XYZ PCS in encoded in 1.15 format, and the matrix input should come in 0..0xffff range, so // we need to adjust the input by a << 1 to obtain a 1.16 fixed and then by a factor of // (0xffff/0x10000) to put data in 0..0xffff range. Total factor is (2.0*65535.0)/65536.0; for (i=0; i < 3; i++) for (j=0; j < 3; j++) Inv.v[i].n[j] *= OutpAdj; Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag); Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag); Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag); if (!Shapes[0] || !Shapes[1] || !Shapes[2]) return NULL; InvShapes[0] = cmsReverseToneCurve(Shapes[0]); InvShapes[1] = cmsReverseToneCurve(Shapes[1]); InvShapes[2] = cmsReverseToneCurve(Shapes[2]); if (!InvShapes[0] || !InvShapes[1] || !InvShapes[2]) { return NULL; } Lut = cmsPipelineAlloc(ContextID, 3, 3); if (Lut != NULL) { // Note that it is certainly possible a single profile would have a LUT based // tag for output working in lab and a matrix-shaper for the fallback cases. // This is not allowed by the spec, but this code is tolerant to those cases if (cmsGetPCS(hProfile) == cmsSigLabData) { cmsPipelineInsertStage(Lut, cmsAT_END, _cmsStageAllocLab2XYZ(ContextID)); } cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Inv, NULL)); cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocToneCurves(ContextID, 3, InvShapes)); } cmsFreeToneCurveTriple(InvShapes); return Lut; }
// Solve a system in the form Ax = b cmsBool CMSEXPORT _cmsMAT3solve(cmsVEC3* x, cmsMAT3* a, cmsVEC3* b) { cmsMAT3 m, a_1; memmove(&m, a, sizeof(cmsMAT3)); if (!_cmsMAT3inverse(&m, &a_1)) return FALSE; // Singular matrix _cmsMAT3eval(x, &a_1, b); return TRUE; }
static cmsPipeline* BuildRGBOutputMatrixShaper(cmsHPROFILE hProfile) { cmsPipeline* Lut; cmsToneCurve *Shapes[3], *InvShapes[3]; cmsMAT3 Mat, Inv; int i, j; cmsContext ContextID = cmsGetProfileContextID(hProfile); if (!ReadICCMatrixRGB2XYZ(&Mat, hProfile)) return NULL; if (!_cmsMAT3inverse(&Mat, &Inv)) return NULL; // XYZ PCS in encoded in 1.15 format, and the matrix input should come in 0..0xffff range, so // we need to adjust the input by a << 1 to obtain a 1.16 fixed and then by a factor of // (0xffff/0x10000) to put data in 0..0xffff range. Total factor is (2.0*65535.0)/65536.0; for (i=0; i < 3; i++) for (j=0; j < 3; j++) Inv.v[i].n[j] *= OutpAdj; Shapes[0] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigRedTRCTag); Shapes[1] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigGreenTRCTag); Shapes[2] = (cmsToneCurve *) cmsReadTag(hProfile, cmsSigBlueTRCTag); if (!Shapes[0] || !Shapes[1] || !Shapes[2]) return NULL; InvShapes[0] = cmsReverseToneCurve(Shapes[0]); InvShapes[1] = cmsReverseToneCurve(Shapes[1]); InvShapes[2] = cmsReverseToneCurve(Shapes[2]); if (!InvShapes[0] || !InvShapes[1] || !InvShapes[2]) { return NULL; } Lut = cmsPipelineAlloc(ContextID, 3, 3); if (Lut != NULL) { cmsPipelineInsertStage(Lut, cmsAT_END, cmsStageAllocMatrix(ContextID, 3, 3, (cmsFloat64Number*) &Inv, NULL)); cmsStage* mpe = cmsStageAllocToneCurves(ContextID, 3, InvShapes); if (mpe) cmsPipelineInsertStage(Lut, cmsAT_END, mpe); else { cmsPipelineFree(Lut); Lut = NULL; } } cmsFreeToneCurveTriple(InvShapes); return Lut; }
// Build a White point, primary chromas transfer matrix from RGB to CIE XYZ // This is just an approximation, I am not handling all the non-linear // aspects of the RGB to XYZ process, and assumming that the gamma correction // has transitive property in the transformation chain. // // the alghoritm: // // - First I build the absolute conversion matrix using // primaries in XYZ. This matrix is next inverted // - Then I eval the source white point across this matrix // obtaining the coeficients of the transformation // - Then, I apply these coeficients to the original matrix // cmsBool _cmsBuildRGB2XYZtransferMatrix(cmsMAT3* r, const cmsCIExyY* WhitePt, const cmsCIExyYTRIPLE* Primrs) { cmsVEC3 WhitePoint, Coef; cmsMAT3 Result, Primaries; cmsFloat64Number xn, yn; cmsFloat64Number xr, yr; cmsFloat64Number xg, yg; cmsFloat64Number xb, yb; xn = WhitePt -> x; yn = WhitePt -> y; xr = Primrs -> Red.x; yr = Primrs -> Red.y; xg = Primrs -> Green.x; yg = Primrs -> Green.y; xb = Primrs -> Blue.x; yb = Primrs -> Blue.y; // Build Primaries matrix _cmsVEC3init(&Primaries.v[0], xr, xg, xb); _cmsVEC3init(&Primaries.v[1], yr, yg, yb); _cmsVEC3init(&Primaries.v[2], (1-xr-yr), (1-xg-yg), (1-xb-yb)); // Result = Primaries ^ (-1) inverse matrix if (!_cmsMAT3inverse(&Primaries, &Result)) return FALSE; _cmsVEC3init(&WhitePoint, xn/yn, 1.0, (1.0-xn-yn)/yn); // Across inverse primaries ... _cmsMAT3eval(&Coef, &Result, &WhitePoint); // Give us the Coefs, then I build transformation matrix _cmsVEC3init(&r -> v[0], Coef.n[VX]*xr, Coef.n[VY]*xg, Coef.n[VZ]*xb); _cmsVEC3init(&r -> v[1], Coef.n[VX]*yr, Coef.n[VY]*yg, Coef.n[VZ]*yb); _cmsVEC3init(&r -> v[2], Coef.n[VX]*(1.0-xr-yr), Coef.n[VY]*(1.0-xg-yg), Coef.n[VZ]*(1.0-xb-yb)); return _cmsAdaptMatrixToD50(r, WhitePt); }
static cmsBool ComputeChromaticAdaptation(cmsMAT3* Conversion, const cmsCIEXYZ* SourceWhitePoint, const cmsCIEXYZ* DestWhitePoint, const cmsMAT3* Chad) { cmsMAT3 Chad_Inv; cmsVEC3 ConeSourceXYZ, ConeSourceRGB; cmsVEC3 ConeDestXYZ, ConeDestRGB; cmsMAT3 Cone, Tmp; Tmp = *Chad; if (!_cmsMAT3inverse(&Tmp, &Chad_Inv)) return FALSE; _cmsVEC3init(&ConeSourceXYZ, SourceWhitePoint -> X, SourceWhitePoint -> Y, SourceWhitePoint -> Z); _cmsVEC3init(&ConeDestXYZ, DestWhitePoint -> X, DestWhitePoint -> Y, DestWhitePoint -> Z); _cmsMAT3eval(&ConeSourceRGB, Chad, &ConeSourceXYZ); _cmsMAT3eval(&ConeDestRGB, Chad, &ConeDestXYZ); // Build matrix _cmsVEC3init(&Cone.v[0], ConeDestRGB.n[0]/ConeSourceRGB.n[0], 0.0, 0.0); _cmsVEC3init(&Cone.v[1], 0.0, ConeDestRGB.n[1]/ConeSourceRGB.n[1], 0.0); _cmsVEC3init(&Cone.v[2], 0.0, 0.0, ConeDestRGB.n[2]/ConeSourceRGB.n[2]); // Normalize _cmsMAT3per(&Tmp, &Cone, Chad); _cmsMAT3per(Conversion, &Chad_Inv, &Tmp); 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; }