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 void ComputeBlackPointCompensationFactors(LPcmsCIEXYZ BlackPointIn, LPcmsCIEXYZ WhitePointIn, LPcmsCIEXYZ IlluminantIn, LPcmsCIEXYZ BlackPointOut, LPcmsCIEXYZ WhitePointOut, LPcmsCIEXYZ IlluminantOut, LPMAT3 m, LPVEC3 of) { cmsCIEXYZ RelativeBlackPointIn, RelativeBlackPointOut; double ax, ay, az, bx, by, bz, tx, ty, tz; // At first, convert both black points to relative. cmsAdaptToIlluminant(&RelativeBlackPointIn, WhitePointIn, IlluminantIn, BlackPointIn); cmsAdaptToIlluminant(&RelativeBlackPointOut, WhitePointOut, IlluminantOut, BlackPointOut); // Now we need to compute a matrix plus an offset m and of such of // [m]*bpin + off = bpout // [m]*D50 + off = D50 // // This is a linear scaling in the form ax+b, where // a = (bpout - D50) / (bpin - D50) // b = - D50* (bpout - bpin) / (bpin - D50) tx = RelativeBlackPointIn.X - IlluminantIn ->X; ty = RelativeBlackPointIn.Y - IlluminantIn ->Y; tz = RelativeBlackPointIn.Z - IlluminantIn ->Z; ax = (RelativeBlackPointOut.X - IlluminantOut ->X) / tx; ay = (RelativeBlackPointOut.Y - IlluminantOut ->Y) / ty; az = (RelativeBlackPointOut.Z - IlluminantOut ->Z) / tz; bx = - IlluminantOut -> X * (RelativeBlackPointOut.X - RelativeBlackPointIn.X) / tx; by = - IlluminantOut -> Y * (RelativeBlackPointOut.Y - RelativeBlackPointIn.Y) / ty; bz = - IlluminantOut -> Z * (RelativeBlackPointOut.Z - RelativeBlackPointIn.Z) / tz; MAT3identity(m); m->v[VX].n[0] = ax; m->v[VY].n[1] = ay; m->v[VZ].n[2] = az; VEC3init(of, bx, by, bz); }
static int BlackPointUsingPerceptualBlack(LPcmsCIEXYZ BlackPoint, cmsHPROFILE hProfile, DWORD dwFlags) { cmsHTRANSFORM hPercLab2CMYK, hRelColCMYK2Lab; cmsHPROFILE hLab; cmsCIELab LabIn, LabOut; WORD CMYK[MAXCHANNELS]; cmsCIEXYZ BlackXYZ, MediaWhite; if (!cmsIsIntentSupported(hProfile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT)) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return 0; } hLab = cmsCreateLabProfile(NULL); hPercLab2CMYK = cmsCreateTransform(hLab, TYPE_Lab_DBL, hProfile, TYPE_CMYK_16, INTENT_PERCEPTUAL, cmsFLAGS_NOTPRECALC); hRelColCMYK2Lab = cmsCreateTransform(hProfile, TYPE_CMYK_16, hLab, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOTPRECALC); LabIn.L = LabIn.a = LabIn.b = 0; cmsDoTransform(hPercLab2CMYK, &LabIn, CMYK, 1); cmsDoTransform(hRelColCMYK2Lab, CMYK, &LabOut, 1); if (LabOut.L > 50) LabOut.L = 50; LabOut.a = LabOut.b = 0; cmsDeleteTransform(hPercLab2CMYK); cmsDeleteTransform(hRelColCMYK2Lab); cmsCloseProfile(hLab); cmsLab2XYZ(NULL, &BlackXYZ, &LabOut); if (!(dwFlags & LCMS_BPFLAGS_D50_ADAPTED)){ cmsTakeMediaWhitePoint(&MediaWhite, hProfile); cmsAdaptToIlluminant(BlackPoint, cmsD50_XYZ(), &MediaWhite, &BlackXYZ); } else *BlackPoint = BlackXYZ; return 1; }
// Get Perceptual black of v4 profiles. static int GetV4PerceptualBlack(LPcmsCIEXYZ BlackPoint, cmsHPROFILE hProfile, DWORD dwFlags) { if (dwFlags & LCMS_BPFLAGS_D50_ADAPTED) { BlackPoint->X = PERCEPTUAL_BLACK_X; BlackPoint->Y = PERCEPTUAL_BLACK_Y; BlackPoint->Z = PERCEPTUAL_BLACK_Z; } else { cmsCIEXYZ D50BlackPoint, MediaWhite; cmsTakeMediaWhitePoint(&MediaWhite, hProfile); D50BlackPoint.X = PERCEPTUAL_BLACK_X; D50BlackPoint.Y = PERCEPTUAL_BLACK_Y; D50BlackPoint.Z = PERCEPTUAL_BLACK_Z; cmsAdaptToIlluminant(BlackPoint, cmsD50_XYZ(), &MediaWhite, &D50BlackPoint); } return 1; }
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; }
static int BlackPointAsDarkerColorant(cmsHPROFILE hInput, int Intent, LPcmsCIEXYZ BlackPoint, DWORD dwFlags) { WORD *Black, *White; cmsHTRANSFORM xform; icColorSpaceSignature Space; int nChannels; DWORD dwFormat; cmsHPROFILE hLab; cmsCIELab Lab; cmsCIEXYZ BlackXYZ, MediaWhite; // If the profile does not support input direction, assume Black point 0 if (!cmsIsIntentSupported(hInput, Intent, LCMS_USED_AS_INPUT)) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return 0; } // Try to get black by using black colorant Space = cmsGetColorSpace(hInput); if (!_cmsEndPointsBySpace(Space, &White, &Black, &nChannels)) { BlackPoint -> X = BlackPoint ->Y = BlackPoint -> Z = 0.0; return 0; } dwFormat = CHANNELS_SH(nChannels)|BYTES_SH(2); hLab = cmsCreateLabProfile(NULL); xform = cmsCreateTransform(hInput, dwFormat, hLab, TYPE_Lab_DBL, Intent, cmsFLAGS_NOTPRECALC); cmsDoTransform(xform, Black, &Lab, 1); // Force it to be neutral, clip to max. L* of 50 Lab.a = Lab.b = 0; if (Lab.L > 50) Lab.L = 50; cmsCloseProfile(hLab); cmsDeleteTransform(xform); cmsLab2XYZ(NULL, &BlackXYZ, &Lab); if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) { *BlackPoint = BlackXYZ; } else { if (!(dwFlags & LCMS_BPFLAGS_D50_ADAPTED)) { cmsTakeMediaWhitePoint(&MediaWhite, hInput); cmsAdaptToIlluminant(BlackPoint, cmsD50_XYZ(), &MediaWhite, &BlackXYZ); } else *BlackPoint = BlackXYZ; } return 1; }
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); }