int cmsDetectBlackPoint(LPcmsCIEXYZ BlackPoint, cmsHPROFILE hProfile, int Intent, DWORD dwFlags) { // v4 + perceptual & saturation intents does have its own black point if ((cmsGetProfileICCversion(hProfile) >= 0x4000000) && (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) { return GetV4PerceptualBlack(BlackPoint, hProfile, dwFlags); } #ifdef HONOR_BLACK_POINT_TAG // v2, v4 rel/abs colorimetric if (cmsIsTag(hProfile, icSigMediaBlackPointTag) && Intent == INTENT_RELATIVE_COLORIMETRIC) { cmsCIEXYZ BlackXYZ, UntrustedBlackPoint, TrustedBlackPoint, MediaWhite; cmsCIELab Lab; // If black point is specified, then use it, cmsTakeMediaBlackPoint(&BlackXYZ, hProfile); cmsTakeMediaWhitePoint(&MediaWhite, hProfile); // Black point is absolute XYZ, so adapt to D50 to get PCS value cmsAdaptToIlluminant(&UntrustedBlackPoint, &MediaWhite, cmsD50_XYZ(), &BlackXYZ); // Force a=b=0 to get rid of any chroma cmsXYZ2Lab(NULL, &Lab, &UntrustedBlackPoint); Lab.a = Lab.b = 0; if (Lab.L > 50) Lab.L = 50; // Clip to L* <= 50 cmsLab2XYZ(NULL, &TrustedBlackPoint, &Lab); // Return BP as D50 relative or absolute XYZ (depends on flags) if (!(dwFlags & LCMS_BPFLAGS_D50_ADAPTED)) cmsAdaptToIlluminant(BlackPoint, cmsD50_XYZ(), &MediaWhite, &TrustedBlackPoint); else *BlackPoint = TrustedBlackPoint; } #endif // If output profile, discount ink-limiting if (Intent == INTENT_RELATIVE_COLORIMETRIC && (cmsGetDeviceClass(hProfile) == icSigOutputClass) && (cmsGetColorSpace(hProfile) == icSigCmykData)) return BlackPointUsingPerceptualBlack(BlackPoint, hProfile, dwFlags); // Nope, compute BP using current intent. return BlackPointAsDarkerColorant(hProfile, Intent, BlackPoint, dwFlags); }
static int bchswSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo) { cmsCIELab LabIn, LabOut; cmsCIELCh LChIn, LChOut; cmsCIEXYZ XYZ; LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo; cmsLabEncoded2Float(&LabIn, In); cmsLab2LCh(&LChIn, &LabIn); // Do some adjusts on LCh LChOut.L = LChIn.L * bchsw ->Contrast + bchsw ->Brightness; LChOut.C = LChIn.C + bchsw -> Saturation; LChOut.h = LChIn.h + bchsw -> Hue; cmsLCh2Lab(&LabOut, &LChOut); // Move white point in Lab cmsLab2XYZ(&bchsw ->WPsrc, &XYZ, &LabOut); cmsXYZ2Lab(&bchsw ->WPdest, &LabOut, &XYZ); // Back to encoded cmsFloat2LabEncoded(Out, &LabOut); return TRUE; }
static int bchswSampler(register WORD In[], register WORD Out[], register LPVOID Cargo) { cmsCIELab LabIn, LabOut; cmsCIELCh LChIn, LChOut; cmsCIEXYZ XYZ; double l; double power; gboolean shift; LPBCHSWADJUSTS bchsw = (LPBCHSWADJUSTS) Cargo; cmsLabEncoded2Float(&LabIn, In); // Move white point in Lab cmsLab2XYZ(&bchsw ->WPsrc, &XYZ, &LabIn); cmsXYZ2Lab(&bchsw ->WPdest, &LabIn, &XYZ); shift = (LabIn.L > 0.5); l = LabIn.L / 100; if (shift) l = 1.0 - l; if (l < 0.0) l = 0.0; if (bchsw->Contrast < 0) power = 1.0 + bchsw->Contrast; else power = (bchsw->Contrast == 1.0) ? 127 : 1.0 / (1.0 - bchsw->Contrast); l = 0.5 * pow (l * 2.0 , power); if (shift) l = 1.0 - l; LabIn.L = l * 100; cmsLab2LCh(&LChIn, &LabIn); // Do some adjusts on LCh LChOut.L = LChIn.L * bchsw ->Exposure + bchsw ->Brightness; LChOut.C = MAX (0, LChIn.C + bchsw ->Saturation); LChOut.h = LChIn.h + bchsw ->Hue; cmsLCh2Lab(&LabOut, &LChOut); // Back to encoded cmsFloat2LabEncoded(Out, &LabOut); return TRUE; }
ScLab ScSpectralValuesConvertor::toLab(const QMap<int, double>& spectrum) const { ScXYZ cieXYZ = toXYZ(spectrum); cmsCIEXYZ cmsXYZ = { cieXYZ.X, cieXYZ.Y, cieXYZ.Z }; cmsCIEXYZ cmsWhite = { m_illuminantWhite.X, m_illuminantWhite.Y, m_illuminantWhite.Z }; cmsCIELab cmsLab = { 0.0, 0.0, 0.0 }; cmsXYZ2Lab(&cmsWhite, &cmsLab, &cmsXYZ); ScLab cieLab = { cmsLab.L, cmsLab.a, cmsLab.b }; return cieLab; }
ScLab ScSpectralValuesConvertor::toLab(const QVector<int>& wavelengths, const QVector<double>& reflectances) const { ScXYZ cieXYZ = toXYZ(wavelengths, reflectances); cmsCIEXYZ cmsXYZ = { cieXYZ.X, cieXYZ.Y, cieXYZ.Z }; cmsCIEXYZ cmsWhite = { m_illuminantWhite.X, m_illuminantWhite.Y, m_illuminantWhite.Z }; cmsCIELab cmsLab = { 0.0, 0.0, 0.0 }; cmsXYZ2Lab(&cmsWhite, &cmsLab, &cmsXYZ); ScLab cieLab = { cmsLab.L, cmsLab.a, cmsLab.b }; return cieLab; }
static int RegressionSamplerA2B(register WORD In[], register WORD Out[], register LPVOID Cargo) { cmsCIEXYZ xyz; cmsCIELab Lab; VEC3 RGB, RGBlinear, vxyz; LPMONITORPROFILERDATA sys = (LPMONITORPROFILERDATA) Cargo; RGB.n[0] = _cmsxSaturate65535To255(In[0]); RGB.n[1] = _cmsxSaturate65535To255(In[1]); RGB.n[2] = _cmsxSaturate65535To255(In[2]); cmsxApplyLinearizationTable(RGB.n, sys->PreLab, RGBlinear.n); cmsxApplyLinearizationTable(RGBlinear.n, sys->Prelinearization, RGBlinear.n); RGBlinear.n[0] /= 255.; RGBlinear.n[1] /= 255.; RGBlinear.n[2] /= 255.; MAT3eval(&vxyz, &sys->PrimariesMatrix, &RGBlinear); xyz.X = vxyz.n[0]; xyz.Y = vxyz.n[1]; xyz.Z = vxyz.n[2]; cmsxChromaticAdaptationAndNormalization(&sys ->hdr, &xyz, false); /* To PCS encoding */ cmsXYZ2Lab(NULL, &Lab, &xyz); cmsFloat2LabEncoded(Out, &Lab); return true; /* And done witch success */ }
cmsBool CMSEXPORT cmsDetectBlackPoint(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags) { // Zero for black point if (cmsGetDeviceClass(hProfile) == cmsSigLinkClass) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // v4 + perceptual & saturation intents does have its own black point, and it is // well specified enough to use it. Black point tag is deprecated in V4. if ((cmsGetEncodedICCversion(hProfile) >= 0x4000000) && (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) { // Matrix shaper share MRC & perceptual intents if (cmsIsMatrixShaper(hProfile)) return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, 0); // Get Perceptual black out of v4 profiles. That is fixed for perceptual & saturation intents BlackPoint -> X = cmsPERCEPTUAL_BLACK_X; BlackPoint -> Y = cmsPERCEPTUAL_BLACK_Y; BlackPoint -> Z = cmsPERCEPTUAL_BLACK_Z; return TRUE; } #ifdef CMS_USE_PROFILE_BLACK_POINT_TAG // v2, v4 rel/abs colorimetric if (cmsIsTag(hProfile, cmsSigMediaBlackPointTag) && Intent == INTENT_RELATIVE_COLORIMETRIC) { cmsCIEXYZ *BlackPtr, BlackXYZ, UntrustedBlackPoint, TrustedBlackPoint, MediaWhite; cmsCIELab Lab; // If black point is specified, then use it, BlackPtr = cmsReadTag(hProfile, cmsSigMediaBlackPointTag); if (BlackPtr != NULL) { BlackXYZ = *BlackPtr; _cmsReadMediaWhitePoint(&MediaWhite, hProfile); // Black point is absolute XYZ, so adapt to D50 to get PCS value cmsAdaptToIlluminant(&UntrustedBlackPoint, &MediaWhite, cmsD50_XYZ(), &BlackXYZ); // Force a=b=0 to get rid of any chroma cmsXYZ2Lab(NULL, &Lab, &UntrustedBlackPoint); Lab.a = Lab.b = 0; if (Lab.L > 50) Lab.L = 50; // Clip to L* <= 50 cmsLab2XYZ(NULL, &TrustedBlackPoint, &Lab); if (BlackPoint != NULL) *BlackPoint = TrustedBlackPoint; return TRUE; } } #endif // That is about v2 profiles. // If output profile, discount ink-limiting and that's all if (Intent == INTENT_RELATIVE_COLORIMETRIC && (cmsGetDeviceClass(hProfile) == cmsSigOutputClass) && (cmsGetColorSpace(hProfile) == cmsSigCmykData)) return BlackPointUsingPerceptualBlack(BlackPoint, hProfile); // Nope, compute BP using current intent. return BlackPointAsDarkerColorant(hProfile, Intent, BlackPoint, dwFlags); }
// Calculates the black point of a destination profile. // This algorithm comes from the Adobe paper disclosing its black point compensation method. cmsBool CMSEXPORT cmsDetectDestinationBlackPoint(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags) { cmsColorSpaceSignature ColorSpace; cmsHTRANSFORM hRoundTrip = NULL; cmsCIELab InitialLab, destLab, Lab; cmsFloat64Number inRamp[256], outRamp[256]; cmsFloat64Number MinL, MaxL; cmsBool NearlyStraightMidrange = TRUE; cmsFloat64Number yRamp[256]; cmsFloat64Number x[256], y[256]; cmsFloat64Number lo, hi; int n, l; cmsProfileClassSignature devClass; // Make sure the device class is adequate devClass = cmsGetDeviceClass(hProfile); if (devClass == cmsSigLinkClass || devClass == cmsSigAbstractClass || devClass == cmsSigNamedColorClass) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // Make sure intent is adequate if (Intent != INTENT_PERCEPTUAL && Intent != INTENT_RELATIVE_COLORIMETRIC && Intent != INTENT_SATURATION) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // v4 + perceptual & saturation intents does have its own black point, and it is // well specified enough to use it. Black point tag is deprecated in V4. if ((cmsGetEncodedICCversion(hProfile) >= 0x4000000) && (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) { // Matrix shaper share MRC & perceptual intents if (cmsIsMatrixShaper(hProfile)) return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, 0); // Get Perceptual black out of v4 profiles. That is fixed for perceptual & saturation intents BlackPoint -> X = cmsPERCEPTUAL_BLACK_X; BlackPoint -> Y = cmsPERCEPTUAL_BLACK_Y; BlackPoint -> Z = cmsPERCEPTUAL_BLACK_Z; return TRUE; } // Check if the profile is lut based and gray, rgb or cmyk (7.2 in Adobe's document) ColorSpace = cmsGetColorSpace(hProfile); if (!cmsIsCLUT(hProfile, Intent, LCMS_USED_AS_OUTPUT ) || (ColorSpace != cmsSigGrayData && ColorSpace != cmsSigRgbData && ColorSpace != cmsSigCmykData)) { // In this case, handle as input case return cmsDetectBlackPoint(BlackPoint, hProfile, Intent, dwFlags); } // It is one of the valid cases!, use Adobe algorithm // Set a first guess, that should work on good profiles. if (Intent == INTENT_RELATIVE_COLORIMETRIC) { cmsCIEXYZ IniXYZ; // calculate initial Lab as source black point if (!cmsDetectBlackPoint(&IniXYZ, hProfile, Intent, dwFlags)) { return FALSE; } // convert the XYZ to lab cmsXYZ2Lab(NULL, &InitialLab, &IniXYZ); } else { // set the initial Lab to zero, that should be the black point for perceptual and saturation InitialLab.L = 0; InitialLab.a = 0; InitialLab.b = 0; } // Step 2 // ====== // Create a roundtrip. Define a Transform BT for all x in L*a*b* hRoundTrip = CreateRoundtripXForm(hProfile, Intent); if (hRoundTrip == NULL) return FALSE; // Compute ramps for (l=0; l < 256; l++) { Lab.L = (cmsFloat64Number) (l * 100.0) / 255.0; Lab.a = cmsmin(50, cmsmax(-50, InitialLab.a)); Lab.b = cmsmin(50, cmsmax(-50, InitialLab.b)); cmsDoTransform(hRoundTrip, &Lab, &destLab, 1); inRamp[l] = Lab.L; outRamp[l] = destLab.L; } // Make monotonic for (l = 254; l > 0; --l) { outRamp[l] = cmsmin(outRamp[l], outRamp[l+1]); } // Check if (! (outRamp[0] < outRamp[255])) { cmsDeleteTransform(hRoundTrip); BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // Test for mid range straight (only on relative colorimetric) NearlyStraightMidrange = TRUE; MinL = outRamp[0]; MaxL = outRamp[255]; if (Intent == INTENT_RELATIVE_COLORIMETRIC) { for (l=0; l < 256; l++) { if (! ((inRamp[l] <= MinL + 0.2 * (MaxL - MinL) ) || (fabs(inRamp[l] - outRamp[l]) < 4.0 ))) NearlyStraightMidrange = FALSE; } // If the mid range is straight (as determined above) then the // DestinationBlackPoint shall be the same as initialLab. // Otherwise, the DestinationBlackPoint shall be determined // using curve fitting. if (NearlyStraightMidrange) { cmsLab2XYZ(NULL, BlackPoint, &InitialLab); cmsDeleteTransform(hRoundTrip); return TRUE; } } // curve fitting: The round-trip curve normally looks like a nearly constant section at the black point, // with a corner and a nearly straight line to the white point. for (l=0; l < 256; l++) { yRamp[l] = (outRamp[l] - MinL) / (MaxL - MinL); } // find the black point using the least squares error quadratic curve fitting if (Intent == INTENT_RELATIVE_COLORIMETRIC) { lo = 0.1; hi = 0.5; } else { // Perceptual and saturation lo = 0.03; hi = 0.25; } // Capture shadow points for the fitting. n = 0; for (l=0; l < 256; l++) { cmsFloat64Number ff = yRamp[l]; if (ff >= lo && ff < hi) { x[n] = inRamp[l]; y[n] = yRamp[l]; n++; } } // No suitable points if (n < 3 ) { cmsDeleteTransform(hRoundTrip); BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // fit and get the vertex of quadratic curve Lab.L = RootOfLeastSquaresFitQuadraticCurve(n, x, y); if (Lab.L < 0.0) { // clip to zero L* if the vertex is negative Lab.L = 0; } Lab.a = InitialLab.a; Lab.b = InitialLab.b; cmsLab2XYZ(NULL, BlackPoint, &Lab); cmsDeleteTransform(hRoundTrip); return TRUE; }
// Calculates the black point of a destination profile. // This algorithm comes from the Adobe paper disclosing its black point compensation method. cmsBool CMSEXPORT cmsDetectDestinationBlackPoint(cmsCIEXYZ* BlackPoint, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number dwFlags) { cmsColorSpaceSignature ColorSpace; cmsHTRANSFORM hRoundTrip = NULL; cmsCIELab InitialLab, destLab, Lab; cmsFloat64Number MinL, MaxL; cmsBool NearlyStraightMidRange = FALSE; cmsFloat64Number L; cmsFloat64Number x[101], y[101]; cmsFloat64Number lo, hi, NonMonoMin; int n, l, i, NonMonoIndx; // Make sure intent is adequate if (Intent != INTENT_PERCEPTUAL && Intent != INTENT_RELATIVE_COLORIMETRIC && Intent != INTENT_SATURATION) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return FALSE; } // v4 + perceptual & saturation intents does have its own black point, and it is // well specified enough to use it. Black point tag is deprecated in V4. if ((cmsGetEncodedICCversion(hProfile) >= 0x4000000) && (Intent == INTENT_PERCEPTUAL || Intent == INTENT_SATURATION)) { // Matrix shaper share MRC & perceptual intents if (cmsIsMatrixShaper(hProfile)) return BlackPointAsDarkerColorant(hProfile, INTENT_RELATIVE_COLORIMETRIC, BlackPoint, 0); // Get Perceptual black out of v4 profiles. That is fixed for perceptual & saturation intents BlackPoint -> X = cmsPERCEPTUAL_BLACK_X; BlackPoint -> Y = cmsPERCEPTUAL_BLACK_Y; BlackPoint -> Z = cmsPERCEPTUAL_BLACK_Z; return TRUE; } // Check if the profile is lut based and gray, rgb or cmyk (7.2 in Adobe's document) ColorSpace = cmsGetColorSpace(hProfile); if (!cmsIsCLUT(hProfile, Intent, LCMS_USED_AS_OUTPUT ) || (ColorSpace != cmsSigGrayData && ColorSpace != cmsSigRgbData && ColorSpace != cmsSigCmykData)) { // In this case, handle as input case return cmsDetectBlackPoint(BlackPoint, hProfile, Intent, dwFlags); } // It is one of the valid cases!, presto chargo hocus pocus, go for the Adobe magic // Step 1 // ====== // Set a first guess, that should work on good profiles. if (Intent == INTENT_RELATIVE_COLORIMETRIC) { cmsCIEXYZ IniXYZ; // calculate initial Lab as source black point if (!cmsDetectBlackPoint(&IniXYZ, hProfile, Intent, dwFlags)) { return FALSE; } // convert the XYZ to lab cmsXYZ2Lab(NULL, &InitialLab, &IniXYZ); } else { // set the initial Lab to zero, that should be the black point for perceptual and saturation InitialLab.L = 0; InitialLab.a = 0; InitialLab.b = 0; } // Step 2 // ====== // Create a roundtrip. Define a Transform BT for all x in L*a*b* hRoundTrip = CreateRoundtripXForm(hProfile, Intent); if (hRoundTrip == NULL) return FALSE; // Calculate Min L* Lab = InitialLab; Lab.L = 0; cmsDoTransform(hRoundTrip, &Lab, &destLab, 1); MinL = destLab.L; // Calculate Max L* Lab = InitialLab; Lab.L = 100; cmsDoTransform(hRoundTrip, &Lab, &destLab, 1); MaxL = destLab.L; // Step 3 // ====== // check if quadratic estimation needs to be done. if (Intent == INTENT_RELATIVE_COLORIMETRIC) { // Conceptually, this code tests how close the source l and converted L are to one another in the mid-range // of the values. If the converted ramp of L values is close enough to a straight line y=x, then InitialLab // is good enough to be the DestinationBlackPoint, NearlyStraightMidRange = TRUE; for (l=0; l <= 100; l++) { Lab.L = l; Lab.a = InitialLab.a; Lab.b = InitialLab.b; cmsDoTransform(hRoundTrip, &Lab, &destLab, 1); L = destLab.L; // Check the mid range in 20% after MinL if (L > (MinL + 0.2 * (MaxL - MinL))) { // Is close enough? if (fabs(L - l) > 4.0) { // Too far away, profile is buggy! NearlyStraightMidRange = FALSE; break; } } } } else { // Check is always performed for perceptual and saturation intents NearlyStraightMidRange = FALSE; } // If no furter checking is needed, we are done if (NearlyStraightMidRange) { cmsLab2XYZ(NULL, BlackPoint, &InitialLab); cmsDeleteTransform(hRoundTrip); return TRUE; } // The round-trip curve normally looks like a nearly constant section at the black point, // with a corner and a nearly straight line to the white point. // STEP 4 // ======= // find the black point using the least squares error quadratic curve fitting if (Intent == INTENT_RELATIVE_COLORIMETRIC) { lo = 0.1; hi = 0.5; } else { // Perceptual and saturation lo = 0.03; hi = 0.25; } // Capture points for the fitting. n = 0; for (l=0; l <= 100; l++) { cmsFloat64Number ff; Lab.L = (cmsFloat64Number) l; Lab.a = InitialLab.a; Lab.b = InitialLab.b; cmsDoTransform(hRoundTrip, &Lab, &destLab, 1); ff = (destLab.L - MinL)/(MaxL - MinL); if (ff >= lo && ff < hi) { x[n] = Lab.L; y[n] = ff; n++; } } // This part is not on the Adobe paper, but I found is necessary for getting any result. if (IsMonotonic(n, y)) { // Monotonic means lower point is stil valid cmsLab2XYZ(NULL, BlackPoint, &InitialLab); cmsDeleteTransform(hRoundTrip); return TRUE; } // No suitable points, regret and use safer algorithm if (n == 0) { cmsDeleteTransform(hRoundTrip); return cmsDetectBlackPoint(BlackPoint, hProfile, Intent, dwFlags); } NonMonoMin = 100; NonMonoIndx = 0; for (i=0; i < n; i++) { if (y[i] < NonMonoMin) { NonMonoIndx = i; NonMonoMin = y[i]; } } Lab.L = x[NonMonoIndx]; // fit and get the vertex of quadratic curve Lab.L = VertexOfLeastSquaresFitQuadraticCurve(n, x, y); if (Lab.L < 0.0 || Lab.L > 50.0) { // clip to zero L* if the vertex is negative Lab.L = 0; } Lab.a = InitialLab.a; Lab.b = InitialLab.b; cmsLab2XYZ(NULL, BlackPoint, &Lab); cmsDeleteTransform(hRoundTrip); return TRUE; }