// Return the CIE94 Delta E double LCMSEXPORT cmsCIE94DeltaE(LPcmsCIELab Lab1, LPcmsCIELab Lab2) { cmsCIELCh LCh1, LCh2; double dE, dL, dC, dh, dhsq; double c12, sc, sh; if (Lab1 ->L == 0 && Lab2 ->L == 0) return 0; dL = fabs(Lab1 ->L - Lab2 ->L); cmsLab2LCh(&LCh1, Lab1); cmsLab2LCh(&LCh2, Lab2); dC = fabs(LCh1.C - LCh2.C); dE = cmsDeltaE(Lab1, Lab2); dhsq = Sqr(dE) - Sqr(dL) - Sqr(dC); if (dhsq < 0) dh = 0; else dh = pow(dhsq, 0.5); c12 = sqrt(LCh1.C * LCh2.C); sc = 1.0 + (0.048 * c12); sh = 1.0 + (0.014 * c12); return sqrt(Sqr(dL) + Sqr(dC) / Sqr(sc) + Sqr(dh) / Sqr(sh)); }
// bfd - gets BFD(1:1) difference between Lab1, Lab2 double LCMSEXPORT cmsBFDdeltaE(LPcmsCIELab Lab1, LPcmsCIELab Lab2) { double lbfd1,lbfd2,AveC,Aveh,dE,deltaL, deltaC,deltah,dc,t,g,dh,rh,rc,rt,bfd; cmsCIELCh LCh1, LCh2; if (Lab1 ->L == 0 && Lab2 ->L == 0) return 0; lbfd1 = ComputeLBFD(Lab1); lbfd2 = ComputeLBFD(Lab2); deltaL = lbfd2 - lbfd1; cmsLab2LCh(&LCh1, Lab1); cmsLab2LCh(&LCh2, Lab2); deltaC = LCh2.C - LCh1.C; AveC = (LCh1.C+LCh2.C)/2; Aveh = (LCh1.h+LCh2.h)/2; dE = cmsDeltaE(Lab1, Lab2); if (Sqr(dE)>(Sqr(Lab2->L-Lab1->L)+Sqr(deltaC))) deltah = sqrt(Sqr(dE)-Sqr(Lab2->L-Lab1->L)-Sqr(deltaC)); else deltah =0; dc = 0.035 * AveC / (1 + 0.00365 * AveC)+0.521; g = sqrt(Sqr(Sqr(AveC))/(Sqr(Sqr(AveC))+14000)); t = 0.627+(0.055*cos((Aveh-254)/(180/M_PI))- 0.040*cos((2*Aveh-136)/(180/M_PI))+ 0.070*cos((3*Aveh-31)/(180/M_PI))+ 0.049*cos((4*Aveh+114)/(180/M_PI))- 0.015*cos((5*Aveh-103)/(180/M_PI))); dh = dc*(g*t+1-g); rh = -0.260*cos((Aveh-308)/(180/M_PI))- 0.379*cos((2*Aveh-160)/(180/M_PI))- 0.636*cos((3*Aveh+254)/(180/M_PI))+ 0.226*cos((4*Aveh+140)/(180/M_PI))- 0.194*cos((5*Aveh+280)/(180/M_PI)); rc = sqrt((AveC*AveC*AveC*AveC*AveC*AveC)/((AveC*AveC*AveC*AveC*AveC*AveC)+70000000)); rt = rh*rc; bfd = sqrt(Sqr(deltaL)+Sqr(deltaC/dc)+Sqr(deltah/dh)+(rt*(deltaC/dc)*(deltah/dh))); return bfd; }
// cmc - CMC(1:1) difference between Lab1, Lab2 double LCMSEXPORT cmsCMCdeltaE(LPcmsCIELab Lab1, LPcmsCIELab Lab2) { double dE,dL,dC,dh,sl,sc,sh,t,f,cmc; cmsCIELCh LCh1, LCh2; if (Lab1 ->L == 0 && Lab2 ->L == 0) return 0; cmsLab2LCh(&LCh1, Lab1); cmsLab2LCh(&LCh2, Lab2); dL = Lab2->L-Lab1->L; dC = LCh2.C-LCh1.C; dE = cmsDeltaE(Lab1, Lab2); if (Sqr(dE)>(Sqr(dL)+Sqr(dC))) dh = sqrt(Sqr(dE)-Sqr(dL)-Sqr(dC)); else dh =0; if ((LCh1.h > 164) && (LCh1.h<345)) t = 0.56 + fabs(0.2 * cos(((LCh1.h + 168)/(180/M_PI)))); else t = 0.36 + fabs(0.4 * cos(((LCh1.h + 35 )/(180/M_PI)))); sc = 0.0638 * LCh1.C / (1 + 0.0131 * LCh1.C) + 0.638; sl = 0.040975 * Lab1->L /(1 + 0.01765 * Lab1->L); if (Lab1->L<16) sl = 0.511; f = sqrt((LCh1.C * LCh1.C * LCh1.C * LCh1.C)/((LCh1.C * LCh1.C * LCh1.C * LCh1.C)+1900)); sh = sc*(t*f+1-f); cmc = sqrt(Sqr(dL/sl)+Sqr(dC/sc)+Sqr(dh/sh)); return cmc; }
static int GamutSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) { GAMUTCHAIN* t = (GAMUTCHAIN* ) Cargo; cmsCIELab LabIn1, LabOut1; cmsCIELab LabIn2, LabOut2; cmsUInt16Number Proof[cmsMAXCHANNELS], Proof2[cmsMAXCHANNELS]; cmsFloat64Number dE1, dE2, ErrorRatio; // Assume in-gamut by default. ErrorRatio = 1.0; // Convert input to Lab cmsDoTransform(t -> hInput, In, &LabIn1, 1); // converts from PCS to colorant. This always // does return in-gamut values, cmsDoTransform(t -> hForward, &LabIn1, Proof, 1); // Now, do the inverse, from colorant to PCS. cmsDoTransform(t -> hReverse, Proof, &LabOut1, 1); memmove(&LabIn2, &LabOut1, sizeof(cmsCIELab)); // Try again, but this time taking Check as input cmsDoTransform(t -> hForward, &LabOut1, Proof2, 1); cmsDoTransform(t -> hReverse, Proof2, &LabOut2, 1); // Take difference of direct value dE1 = cmsDeltaE(&LabIn1, &LabOut1); // Take difference of converted value dE2 = cmsDeltaE(&LabIn2, &LabOut2); // if dE1 is small and dE2 is small, value is likely to be in gamut if (dE1 < t->Thereshold && dE2 < t->Thereshold) Out[0] = 0; else { // if dE1 is small and dE2 is big, undefined. Assume in gamut if (dE1 < t->Thereshold && dE2 > t->Thereshold) Out[0] = 0; else // dE1 is big and dE2 is small, clearly out of gamut if (dE1 > t->Thereshold && dE2 < t->Thereshold) Out[0] = (cmsUInt16Number) _cmsQuickFloor((dE1 - t->Thereshold) + .5); else { // dE1 is big and dE2 is also big, could be due to perceptual mapping // so take error ratio if (dE2 == 0.0) ErrorRatio = dE1; else ErrorRatio = dE1 / dE2; if (ErrorRatio > t->Thereshold) Out[0] = (cmsUInt16Number) _cmsQuickFloor((ErrorRatio - t->Thereshold) + .5); else Out[0] = 0; } } return TRUE; }
// The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision static int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) { int i; cmsFloat32Number Inf[4], Outf[4]; cmsFloat32Number LabK[4]; cmsFloat64Number SumCMY, SumCMYK, Error, Ratio; cmsCIELab ColorimetricLab, BlackPreservingLab; PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo; // Convert from 16 bits to floating point for (i=0; i < 4; i++) Inf[i] = (cmsFloat32Number) (In[i] / 65535.0); // Get the K across Tone curve LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]); // If going across black only, keep black only if (In[0] == 0 && In[1] == 0 && In[2] == 0) { Out[0] = Out[1] = Out[2] = 0; Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0); return TRUE; } // Try the original transform, cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk); // Store a copy of the floating point result into 16-bit for (i=0; i < 4; i++) Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0); // Maybe K is already ok (mostly on K=0) if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) { return TRUE; } // K differ, mesure and keep Lab measurement for further usage // this is done in relative colorimetric intent cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1); // Is not black only and the transform doesn't keep black. // Obtain the Lab of output CMYK. After that we have Lab + K cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1); // Obtain the corresponding CMY using reverse interpolation // (K is fixed in LabK[3]) if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) { // Cannot find a suitable value, so use colorimetric xform // which is already stored in Out[] return TRUE; } // Make sure to pass thru K (which now is fixed) Outf[3] = LabK[3]; // Apply TAC if needed SumCMY = Outf[0] + Outf[1] + Outf[2]; SumCMYK = SumCMY + Outf[3]; if (SumCMYK > bp ->MaxTAC) { Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY); if (Ratio < 0) Ratio = 0; } else Ratio = 1.0; Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0); // C Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0); // M Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0); // Y Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0); // Estimate the error (this goes 16 bits to Lab DBL) cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1); Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab); if (Error > bp -> MaxError) bp->MaxError = Error; return TRUE; }
double dkCmsDeltaE(LPcmsCIELab Lab1, LPcmsCIELab Lab2) { return static_cast<double>( cmsDeltaE(static_cast<cmsCIELab*>( Lab1 ), static_cast<cmsCIELab*>( Lab2 )) ); }
LCMSAPI double LCMSEXPORT cmsEvalLUTreverse(LPLUT Lut, WORD Target[], WORD Result[], LPWORD Hint) { int i; double error, LastError = 1E20; cmsCIELab fx, Goal; VEC3 tmp, tmp2, x; MAT3 Jacobian; WORD FixedK; WORD LastResult[4]; // This is our Lab goal cmsLabEncoded2Float(&Goal, Target); // Special case for CMYK->Lab if (Lut ->InputChan == 4) FixedK = Target[3]; else FixedK = 0; // Take the hint as starting point if specified if (Hint == NULL) { // Begin at any point, we choose 1/3 of neutral CMY gray x.n[0] = x.n[1] = x.n[2] = 0.3; } else { FromEncoded(&x, Hint); } // Iterate for (i = 0; i < INVERSION_MAX_ITERATIONS; i++) { // Get beginning fx EvalLUTdoubleKLab(Lut, &x, FixedK, &fx); // Compute error error = cmsDeltaE(&fx, &Goal); // If not convergent, return last safe value if (error >= LastError) break; // Keep latest values LastError = error; ToEncoded(LastResult, &x); LastResult[3] = FixedK; // Obtain slope ComputeJacobianLab(Lut, &Jacobian, &x, FixedK); // Solve system tmp2.n[0] = fx.L - Goal.L; tmp2.n[1] = fx.a - Goal.a; tmp2.n[2] = fx.b - Goal.b; if (!MAT3solve(&tmp, &Jacobian, &tmp2)) break; // Move our guess x.n[0] -= tmp.n[0]; x.n[1] -= tmp.n[1]; x.n[2] -= tmp.n[2]; // Some clipping.... VEC3saturate(&x); } Result[0] = LastResult[0]; Result[1] = LastResult[1]; Result[2] = LastResult[2]; Result[3] = LastResult[3]; return LastError; }
static int GamutSampler(register WORD In[], register WORD Out[], register LPVOID Cargo) { LPGAMUTCHAIN t = (LPGAMUTCHAIN) Cargo; WORD Proof[MAXCHANNELS], Check[MAXCHANNELS]; WORD Proof2[MAXCHANNELS], Check2[MAXCHANNELS]; cmsCIELab LabIn1, LabOut1; cmsCIELab LabIn2, LabOut2; double dE1, dE2, ErrorRatio; // Assume in-gamut by default. dE1 = 0.; dE2 = 0; ErrorRatio = 1.0; // Any input space? I can use In[] no matter channels // because is just one pixel if (t -> hInput != NULL) cmsDoTransform(t -> hInput, In, In, 1); // converts from PCS to colorant. This always // does return in-gamut values, cmsDoTransform(t -> hForward, In, Proof, 1); // Now, do the inverse, from colorant to PCS. cmsDoTransform(t -> hReverse, Proof, Check, 1); // Try again, but this time taking Check as input cmsDoTransform(t -> hForward, Check, Proof2, 1); cmsDoTransform(t -> hReverse, Proof2, Check2, 1); // Does the transform returns out-of-gamut? if (Check[0] == 0xFFFF && Check[1] == 0xFFFF && Check[2] == 0xFFFF) Out[0] = 0xFF00; // Out of gamut! else { // Transport encoded values cmsLabEncoded2Float(&LabIn1, In); cmsLabEncoded2Float(&LabOut1, Check); // Take difference of direct value dE1 = cmsDeltaE(&LabIn1, &LabOut1); cmsLabEncoded2Float(&LabIn2, Check); cmsLabEncoded2Float(&LabOut2, Check2); // Take difference of converted value dE2 = cmsDeltaE(&LabIn2, &LabOut2); // if dE1 is small and dE2 is small, value is likely to be in gamut if (dE1 < t->Thereshold && dE2 < t->Thereshold) Out[0] = 0; else // if dE1 is small and dE2 is big, undefined. Assume in gamut if (dE1 < t->Thereshold && dE2 > t->Thereshold) Out[0] = 0; else // dE1 is big and dE2 is small, clearly out of gamut if (dE1 > t->Thereshold && dE2 < t->Thereshold) Out[0] = (WORD) _cmsQuickFloor((dE1 - t->Thereshold) + .5); else { // dE1 is big and dE2 is also big, could be due to perceptual mapping // so take error ratio if (dE2 == 0.0) ErrorRatio = dE1; else ErrorRatio = dE1 / dE2; if (ErrorRatio > t->Thereshold) Out[0] = (WORD) _cmsQuickFloor((ErrorRatio - t->Thereshold) + .5); else Out[0] = 0; } } return TRUE; }