bool LcmsColorProfileContainer::init() { if (d->profile) cmsCloseProfile(d->profile); d->profile = cmsOpenProfileFromMem((void*)d->data->rawData().constData(), d->data->rawData().size()); #ifndef NDEBUG if (d->data->rawData().size() == 4096) { warnPigment << "Profile has a size of 4096, which is suspicious and indicates a possible misuse of QIODevice::read(int), check your code."; } #endif if (d->profile) { wchar_t buffer[_BUFFER_SIZE_]; d->colorSpaceSignature = cmsGetColorSpace(d->profile); d->deviceClass = cmsGetDeviceClass(d->profile); cmsGetProfileInfo(d->profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->productDescription = QString::fromWCharArray(buffer); d->valid = true; cmsGetProfileInfo(d->profile, cmsInfoModel, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->name = QString::fromWCharArray(buffer); cmsGetProfileInfo(d->profile, cmsInfoManufacturer, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->manufacturer = QString::fromWCharArray(buffer); // Check if the profile can convert (something->this) d->suitableForOutput = cmsIsMatrixShaper(d->profile) || ( cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT) && cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT) ); return true; } return false; }
/** * gimp_color_profile_is_linear: * @profile: a #GimpColorProfile * * This function determines is the ICC profile represented by a GimpColorProfile * is a linear RGB profile or not, some profiles that are LUTs though linear * will also return FALSE; * * Return value: %TRUE if the profile is a matrix shaping profile with linear * TRCs, %FALSE otherwise. * * Since: 2.10 **/ gboolean gimp_color_profile_is_linear (GimpColorProfile *profile) { cmsHPROFILE prof; cmsToneCurve *curve; g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile), FALSE); prof = profile->priv->lcms_profile; if (! cmsIsMatrixShaper (prof)) return FALSE; if (cmsIsCLUT (prof, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT)) return FALSE; if (cmsIsCLUT (prof, INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT)) return FALSE; curve = cmsReadTag(prof, cmsSigRedTRCTag); if (curve == NULL || ! cmsIsToneCurveLinear (curve)) return FALSE; curve = cmsReadTag (prof, cmsSigGreenTRCTag); if (curve == NULL || ! cmsIsToneCurveLinear (curve)) return FALSE; curve = cmsReadTag (prof, cmsSigBlueTRCTag); if (curve == NULL || ! cmsIsToneCurveLinear (curve)) return FALSE; return TRUE; }
// Return info about supported intents cmsBool CMSEXPORT cmsIsIntentSupported(cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number UsedDirection) { if (cmsIsCLUT(hProfile, Intent, UsedDirection)) return TRUE; // Is there any matrix-shaper? If so, the intent is supported. This is a bit odd, since V2 matrix shaper // does not fully support relative colorimetric because they cannot deal with non-zero black points, but // many profiles claims that, and this is certainly not true for V4 profiles. Lets answer "yes" no matter // the accuracy would be less than optimal in rel.col and v2 case. return cmsIsMatrixShaper(hProfile); }
bool LcmsColorProfileContainer::init() { if (d->profile) { cmsCloseProfile(d->profile); } d->profile = cmsOpenProfileFromMem((void *)d->data->rawData().constData(), d->data->rawData().size()); #ifndef NDEBUG if (d->data->rawData().size() == 4096) { qWarning() << "Profile has a size of 4096, which is suspicious and indicates a possible misuse of QIODevice::read(int), check your code."; } #endif if (d->profile) { wchar_t buffer[_BUFFER_SIZE_]; d->colorSpaceSignature = cmsGetColorSpace(d->profile); d->deviceClass = cmsGetDeviceClass(d->profile); cmsGetProfileInfo(d->profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->name = QString::fromWCharArray(buffer); //apparantly this should give us a localised string??? Not sure about this. cmsGetProfileInfo(d->profile, cmsInfoModel, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->productDescription = QString::fromWCharArray(buffer); cmsGetProfileInfo(d->profile, cmsInfoManufacturer, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->manufacturer = QString::fromWCharArray(buffer); cmsGetProfileInfo(d->profile, cmsInfoCopyright, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); d->copyright = QString::fromWCharArray(buffer); cmsProfileClassSignature profile_class; profile_class = cmsGetDeviceClass(d->profile); d->valid = (profile_class != cmsSigNamedColorClass); //This is where obtain the whitepoint, and convert it to the actual white point of the profile in the case a Chromatic adaption tag is //present. This is necessary for profiles following the v4 spec. cmsCIEXYZ baseMediaWhitePoint;//dummy to hold copy of mediawhitepoint if this is modified by chromatic adaption. if (cmsIsTag(d->profile, cmsSigMediaWhitePointTag)) { d->mediaWhitePoint = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigMediaWhitePointTag)); baseMediaWhitePoint = d->mediaWhitePoint; cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint); if (cmsIsTag(d->profile, cmsSigChromaticAdaptationTag)) { //the chromatic adaption tag represent a matrix from the actual white point of the profile to D50. cmsCIEXYZ *CAM1 = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigChromaticAdaptationTag); //We first put all our data into structures we can manipulate. double d3dummy [3] = {d->mediaWhitePoint.X, d->mediaWhitePoint.Y, d->mediaWhitePoint.Z}; QGenericMatrix<1, 3, double> whitePointMatrix(d3dummy); QTransform invertDummy(CAM1[0].X, CAM1[0].Y, CAM1[0].Z, CAM1[1].X, CAM1[1].Y, CAM1[1].Z, CAM1[2].X, CAM1[2].Y, CAM1[2].Z); //we then abuse QTransform's invert function because it probably does matrix invertion 20 times better than I can program. //if the matrix is uninvertable, invertedDummy will be an identity matrix, which for us means that it won't give any noticeble //effect when we start multiplying. QTransform invertedDummy = invertDummy.inverted(); //we then put the QTransform into a generic 3x3 matrix. double d9dummy [9] = {invertedDummy.m11(), invertedDummy.m12(), invertedDummy.m13(), invertedDummy.m21(), invertedDummy.m22(), invertedDummy.m23(), invertedDummy.m31(), invertedDummy.m32(), invertedDummy.m33() }; QGenericMatrix<3, 3, double> chromaticAdaptionMatrix(d9dummy); //multiplying our inverted adaption matrix with the whitepoint gives us the right whitepoint. QGenericMatrix<1, 3, double> result = chromaticAdaptionMatrix * whitePointMatrix; //and then we pour the matrix into the whitepoint variable. Generic matrix does row/column for indices even though it //uses column/row for initialising. d->mediaWhitePoint.X = result(0, 0); d->mediaWhitePoint.Y = result(1, 0); d->mediaWhitePoint.Z = result(2, 0); cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint); } } //This is for RGB profiles, but it only works for matrix profiles. Need to design it to work with non-matrix profiles. if (cmsIsTag(d->profile, cmsSigRedColorantTag)) { cmsCIEXYZTRIPLE tempColorants; tempColorants.Red = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigRedColorantTag)); tempColorants.Green = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigGreenColorantTag)); tempColorants.Blue = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigBlueColorantTag)); //convert to d65, this is useless. cmsAdaptToIlluminant(&d->colorants.Red, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Red); cmsAdaptToIlluminant(&d->colorants.Green, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Green); cmsAdaptToIlluminant(&d->colorants.Blue, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Blue); //d->colorants = tempColorants; d->hasColorants = true; } else { //qDebug()<<d->name<<": has no colorants"; d->hasColorants = false; } //retrieve TRC. if (cmsIsTag(d->profile, cmsSigRedTRCTag) && cmsIsTag(d->profile, cmsSigBlueTRCTag) && cmsIsTag(d->profile, cmsSigGreenTRCTag)) { d->redTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigRedTRCTag)); d->greenTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGreenTRCTag)); d->blueTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigBlueTRCTag)); d->redTRCReverse = cmsReverseToneCurve(d->redTRC); d->greenTRCReverse = cmsReverseToneCurve(d->greenTRC); d->blueTRCReverse = cmsReverseToneCurve(d->blueTRC); d->hasTRC = true; } else if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) { d->grayTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGrayTRCTag)); d->grayTRCReverse = cmsReverseToneCurve(d->grayTRC); d->hasTRC = true; } else { d->hasTRC = false; } // Check if the profile can convert (something->this) d->suitableForOutput = cmsIsMatrixShaper(d->profile) || (cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT) && cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT)); d->version = cmsGetProfileVersion(d->profile); d->defaultIntent = cmsGetHeaderRenderingIntent(d->profile); d->isMatrixShaper = cmsIsMatrixShaper(d->profile); d->isPerceptualCLUT = cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT); d->isSaturationCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT); d->isAbsoluteCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT); d->isRelativeCLUT = cmsIsCLUT(d->profile, INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_INPUT); return true; } return false; }
// 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; }