File indexing completed on 2024-06-23 04:26:03
0001 /* 0002 * This file is part of the KDE project 0003 * SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org> 0004 * SPDX-FileCopyrightText: 2001 John Califf 0005 * SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org> 0006 * SPDX-FileCopyrightText: 2007 Thomas Zander <zander@kde.org> 0007 * SPDX-FileCopyrightText: 2007 Adrian Page <adrian@pagenet.plus.com> 0008 * 0009 * SPDX-License-Identifier: LGPL-2.0-or-later 0010 */ 0011 0012 #include "LcmsColorProfileContainer.h" 0013 0014 #include <QGenericMatrix> 0015 #include <QTransform> 0016 #include <array> 0017 #include <cfloat> 0018 #include <cmath> 0019 0020 #include <QDebug> 0021 0022 #include "kis_debug.h" 0023 0024 #include <KisLazyStorage.h> 0025 #include <KisLazyValueWrapper.h> 0026 0027 namespace { 0028 struct ReverseCurveWrapper 0029 { 0030 ReverseCurveWrapper() : reverseCurve(0) {} 0031 0032 explicit ReverseCurveWrapper(cmsToneCurve *curve) { 0033 reverseCurve = cmsReverseToneCurve(curve); 0034 } 0035 0036 ~ReverseCurveWrapper() { 0037 if (reverseCurve) { 0038 cmsFreeToneCurve(reverseCurve); 0039 } 0040 } 0041 0042 operator cmsToneCurve*() const { 0043 return reverseCurve; 0044 } 0045 0046 operator cmsToneCurve*() { 0047 return reverseCurve; 0048 } 0049 0050 ReverseCurveWrapper(const ReverseCurveWrapper&rhs) = delete; 0051 ReverseCurveWrapper& operator=(const ReverseCurveWrapper&rhs) = delete; 0052 0053 ReverseCurveWrapper(ReverseCurveWrapper&&rhs) = default; 0054 ReverseCurveWrapper& operator=(ReverseCurveWrapper&&rhs) = default; 0055 0056 cmsToneCurve *reverseCurve {0}; 0057 }; 0058 } // namespace 0059 0060 class LcmsColorProfileContainer::Private 0061 { 0062 public: 0063 cmsHPROFILE profile; 0064 cmsColorSpaceSignature colorSpaceSignature; 0065 cmsProfileClassSignature deviceClass; 0066 QString productDescription; 0067 QString manufacturer; 0068 QString copyright; 0069 QString name; 0070 float version; 0071 IccColorProfile::Data *data {0}; 0072 bool valid {false}; 0073 bool suitableForOutput {false}; 0074 bool hasColorants; 0075 0076 using LazyBool = KisLazyStorage<KisLazyValueWrapper<bool>, std::function<bool()>>; 0077 0078 LazyBool hasTRC = LazyBool(LazyBool::init_value_tag{}, {}); 0079 LazyBool isLinear = LazyBool(LazyBool::init_value_tag{}, {}); 0080 0081 bool adaptedFromD50; 0082 cmsCIEXYZ mediaWhitePoint; 0083 cmsCIExyY whitePoint; 0084 cmsCIEXYZTRIPLE colorants; 0085 cmsToneCurve *redTRC {0}; 0086 cmsToneCurve *greenTRC {0}; 0087 cmsToneCurve *blueTRC {0}; 0088 cmsToneCurve *grayTRC {0}; 0089 0090 using LazyReverseCurve = KisLazyStorage<ReverseCurveWrapper, cmsToneCurve*>; 0091 0092 LazyReverseCurve redTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {}); 0093 LazyReverseCurve greenTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {}); 0094 LazyReverseCurve blueTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {}); 0095 LazyReverseCurve grayTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {}); 0096 0097 cmsUInt32Number defaultIntent; 0098 bool isPerceptualCLUT; 0099 bool isRelativeCLUT; 0100 bool isAbsoluteCLUT; 0101 bool isSaturationCLUT; 0102 bool isMatrixShaper; 0103 0104 QByteArray uniqueId; 0105 }; 0106 0107 LcmsColorProfileContainer::LcmsColorProfileContainer() 0108 : d(new Private()) 0109 { 0110 d->profile = 0; 0111 } 0112 0113 LcmsColorProfileContainer::LcmsColorProfileContainer(IccColorProfile::Data *data) 0114 : d(new Private()) 0115 { 0116 d->data = data; 0117 d->profile = 0; 0118 init(); 0119 } 0120 0121 QByteArray LcmsColorProfileContainer::lcmsProfileToByteArray(const cmsHPROFILE profile) 0122 { 0123 cmsUInt32Number bytesNeeded = 0; 0124 // Make a raw data image ready for saving 0125 cmsSaveProfileToMem(profile, 0, &bytesNeeded); // calc size 0126 QByteArray rawData; 0127 rawData.resize(bytesNeeded); 0128 if (rawData.size() >= (int)bytesNeeded) { 0129 cmsSaveProfileToMem(profile, rawData.data(), &bytesNeeded); // fill buffer 0130 } else { 0131 qWarning() << "Couldn't resize the profile buffer, system is probably running out of memory."; 0132 rawData.resize(0); 0133 } 0134 return rawData; 0135 } 0136 0137 IccColorProfile *LcmsColorProfileContainer::createFromLcmsProfile(const cmsHPROFILE profile) 0138 { 0139 IccColorProfile *iccprofile = new IccColorProfile(lcmsProfileToByteArray(profile)); 0140 cmsCloseProfile(profile); 0141 return iccprofile; 0142 } 0143 0144 LcmsColorProfileContainer::~LcmsColorProfileContainer() 0145 { 0146 cmsCloseProfile(d->profile); 0147 delete d; 0148 } 0149 0150 #define _BUFFER_SIZE_ 1000 0151 0152 bool LcmsColorProfileContainer::init() 0153 { 0154 if (d->profile) { 0155 cmsCloseProfile(d->profile); 0156 } 0157 0158 d->profile = cmsOpenProfileFromMem((void *)d->data->rawData().constData(), d->data->rawData().size()); 0159 0160 0161 #ifndef NDEBUG 0162 if (d->data->rawData().size() == 4096) { 0163 qWarning() << "Profile has a size of 4096, which is suspicious and indicates a possible misuse of QIODevice::read(int), check your code."; 0164 } 0165 #endif 0166 0167 if (d->profile) { 0168 wchar_t buffer[_BUFFER_SIZE_]; 0169 d->colorSpaceSignature = cmsGetColorSpace(d->profile); 0170 d->deviceClass = cmsGetDeviceClass(d->profile); 0171 cmsGetProfileInfo(d->profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); 0172 d->name = QString::fromWCharArray(buffer); 0173 0174 //apparently this should give us a localised string??? Not sure about this. 0175 cmsGetProfileInfo(d->profile, cmsInfoModel, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); 0176 d->productDescription = QString::fromWCharArray(buffer); 0177 0178 cmsGetProfileInfo(d->profile, cmsInfoManufacturer, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); 0179 d->manufacturer = QString::fromWCharArray(buffer); 0180 0181 cmsGetProfileInfo(d->profile, cmsInfoCopyright, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_); 0182 d->copyright = QString::fromWCharArray(buffer); 0183 0184 cmsProfileClassSignature profile_class; 0185 profile_class = cmsGetDeviceClass(d->profile); 0186 d->valid = ( profile_class != cmsSigNamedColorClass 0187 && profile_class != cmsSigLinkClass); 0188 0189 //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 0190 //present. This is necessary for profiles following the v4 spec. 0191 cmsCIEXYZ baseMediaWhitePoint;//dummy to hold copy of mediawhitepoint if this is modified by chromatic adaption. 0192 cmsCIEXYZ *mediaWhitePointPtr; 0193 bool whiteComp[3]; 0194 bool whiteIsD50; 0195 // Possible bug in profiles: there are in fact some that says they contain that tag 0196 // but in fact the pointer is null. 0197 // Let's not crash on it anyway, and assume there is no white point instead. 0198 // BUG:423685 0199 if (cmsIsTag(d->profile, cmsSigMediaWhitePointTag) 0200 && (mediaWhitePointPtr = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigMediaWhitePointTag))) { 0201 0202 d->mediaWhitePoint = *(mediaWhitePointPtr); 0203 baseMediaWhitePoint = d->mediaWhitePoint; 0204 0205 whiteComp[0] = std::fabs(baseMediaWhitePoint.X - cmsD50_XYZ()->X) < 0.00001; 0206 whiteComp[1] = std::fabs(baseMediaWhitePoint.Y - cmsD50_XYZ()->Y) < 0.00001; 0207 whiteComp[2] = std::fabs(baseMediaWhitePoint.Z - cmsD50_XYZ()->Z) < 0.00001; 0208 whiteIsD50 = std::all_of(std::begin(whiteComp), std::end(whiteComp), [](bool b) {return b;}); 0209 0210 cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint); 0211 cmsCIEXYZ *CAM1; 0212 if (cmsIsTag(d->profile, cmsSigChromaticAdaptationTag) 0213 && (CAM1 = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigChromaticAdaptationTag)) 0214 && whiteIsD50) { 0215 //the chromatic adaption tag represent a matrix from the actual white point of the profile to D50. 0216 0217 //We first put all our data into structures we can manipulate. 0218 double d3dummy [3] = {d->mediaWhitePoint.X, d->mediaWhitePoint.Y, d->mediaWhitePoint.Z}; 0219 QGenericMatrix<1, 3, double> whitePointMatrix(d3dummy); 0220 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); 0221 //we then abuse QTransform's invert function because it probably does matrix inversion 20 times better than I can program. 0222 //if the matrix is uninvertable, invertedDummy will be an identity matrix, which for us means that it won't give any noticeable 0223 //effect when we start multiplying. 0224 QTransform invertedDummy = invertDummy.inverted(); 0225 //we then put the QTransform into a generic 3x3 matrix. 0226 double d9dummy [9] = {invertedDummy.m11(), invertedDummy.m12(), invertedDummy.m13(), 0227 invertedDummy.m21(), invertedDummy.m22(), invertedDummy.m23(), 0228 invertedDummy.m31(), invertedDummy.m32(), invertedDummy.m33() 0229 }; 0230 QGenericMatrix<3, 3, double> chromaticAdaptionMatrix(d9dummy); 0231 //multiplying our inverted adaption matrix with the whitepoint gives us the right whitepoint. 0232 QGenericMatrix<1, 3, double> result = chromaticAdaptionMatrix * whitePointMatrix; 0233 //and then we pour the matrix into the whitepoint variable. Generic matrix does row/column for indices even though it 0234 //uses column/row for initialising. 0235 d->mediaWhitePoint.X = result(0, 0); 0236 d->mediaWhitePoint.Y = result(1, 0); 0237 d->mediaWhitePoint.Z = result(2, 0); 0238 cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint); 0239 } 0240 } 0241 //This is for RGB profiles, but it only works for matrix profiles. Need to design it to work with non-matrix profiles. 0242 cmsCIEXYZ *tempColorantsRed, *tempColorantsGreen, *tempColorantsBlue; 0243 // Note: don't assume that cmsIsTag is enough to check for errors; check the pointers, too 0244 // BUG:423685 0245 if (cmsIsTag(d->profile, cmsSigRedColorantTag) && cmsIsTag(d->profile, cmsSigRedColorantTag) && cmsIsTag(d->profile, cmsSigRedColorantTag) 0246 && (tempColorantsRed = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigRedColorantTag)) 0247 && (tempColorantsGreen = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigGreenColorantTag)) 0248 && (tempColorantsBlue = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigBlueColorantTag))) { 0249 cmsCIEXYZTRIPLE tempColorants; 0250 tempColorants.Red = *tempColorantsRed; 0251 tempColorants.Green = *tempColorantsGreen; 0252 tempColorants.Blue = *tempColorantsBlue; 0253 //convert to d65, this is useless. 0254 cmsAdaptToIlluminant(&d->colorants.Red, cmsD50_XYZ(), &d->mediaWhitePoint, &tempColorants.Red); 0255 cmsAdaptToIlluminant(&d->colorants.Green, cmsD50_XYZ(), &d->mediaWhitePoint, &tempColorants.Green); 0256 cmsAdaptToIlluminant(&d->colorants.Blue, cmsD50_XYZ(), &d->mediaWhitePoint, &tempColorants.Blue); 0257 //d->colorants = tempColorants; 0258 d->hasColorants = true; 0259 } else { 0260 //qDebug()<<d->name<<": has no colorants"; 0261 d->hasColorants = false; 0262 } 0263 //retrieve TRC. 0264 if (cmsIsTag(d->profile, cmsSigRedTRCTag) && cmsIsTag(d->profile, cmsSigBlueTRCTag) && cmsIsTag(d->profile, cmsSigGreenTRCTag)) { 0265 0266 d->redTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigRedTRCTag)); 0267 d->greenTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGreenTRCTag)); 0268 d->blueTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigBlueTRCTag)); 0269 if (d->redTRC) d->redTRCReverse = Private::LazyReverseCurve(d->redTRC); 0270 if (d->greenTRC) d->greenTRCReverse = Private::LazyReverseCurve(d->greenTRC); 0271 if (d->blueTRC) d->blueTRCReverse = Private::LazyReverseCurve(d->blueTRC); 0272 0273 d->hasTRC = Private::LazyBool([d = d] () { 0274 return d->redTRC && d->greenTRC && d->blueTRC && *d->redTRCReverse && *d->greenTRCReverse && *d->blueTRCReverse; 0275 }); 0276 0277 d->isLinear = Private::LazyBool([d = d] () { 0278 return *d->hasTRC 0279 && cmsIsToneCurveLinear(d->redTRC) 0280 && cmsIsToneCurveLinear(d->greenTRC) 0281 && cmsIsToneCurveLinear(d->blueTRC); 0282 }); 0283 0284 } else if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) { 0285 d->grayTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGrayTRCTag)); 0286 if (d->grayTRC) d->grayTRCReverse = Private::LazyReverseCurve(d->grayTRC); 0287 0288 d->hasTRC = Private::LazyBool([d = d] () { 0289 return d->grayTRC && *d->grayTRCReverse; 0290 }); 0291 0292 d->isLinear = Private::LazyBool([d = d] () { 0293 return *d->hasTRC && cmsIsToneCurveLinear(d->grayTRC); 0294 }); 0295 } else { 0296 d->hasTRC = Private::LazyBool(Private::LazyBool::init_value_tag{}, {}); 0297 } 0298 0299 // Check if the profile can convert (something->this) 0300 d->suitableForOutput = cmsIsIntentSupported(d->profile, 0301 INTENT_PERCEPTUAL, 0302 LCMS_USED_AS_OUTPUT); 0303 0304 d->version = cmsGetProfileVersion(d->profile); 0305 d->defaultIntent = cmsGetHeaderRenderingIntent(d->profile); 0306 d->isMatrixShaper = cmsIsMatrixShaper(d->profile); 0307 d->isPerceptualCLUT = cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT); 0308 d->isSaturationCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT); 0309 d->isAbsoluteCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT); 0310 d->isRelativeCLUT = cmsIsCLUT(d->profile, INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_INPUT); 0311 0312 return true; 0313 } 0314 0315 return false; 0316 } 0317 0318 cmsHPROFILE LcmsColorProfileContainer::lcmsProfile() const 0319 { 0320 return d->profile; 0321 } 0322 0323 cmsColorSpaceSignature LcmsColorProfileContainer::colorSpaceSignature() const 0324 { 0325 return d->colorSpaceSignature; 0326 } 0327 0328 cmsProfileClassSignature LcmsColorProfileContainer::deviceClass() const 0329 { 0330 return d->deviceClass; 0331 } 0332 0333 QString LcmsColorProfileContainer::manufacturer() const 0334 { 0335 return d->manufacturer; 0336 } 0337 0338 QString LcmsColorProfileContainer::copyright() const 0339 { 0340 return d->copyright; 0341 } 0342 0343 bool LcmsColorProfileContainer::valid() const 0344 { 0345 return d->valid; 0346 } 0347 0348 float LcmsColorProfileContainer::version() const 0349 { 0350 return d->version; 0351 } 0352 0353 bool LcmsColorProfileContainer::isSuitableForOutput() const 0354 { 0355 return d->suitableForOutput; 0356 } 0357 0358 bool LcmsColorProfileContainer::isSuitableForPrinting() const 0359 { 0360 return deviceClass() == cmsSigOutputClass; 0361 } 0362 0363 bool LcmsColorProfileContainer::isSuitableForDisplay() const 0364 { 0365 return deviceClass() == cmsSigDisplayClass; 0366 } 0367 0368 bool LcmsColorProfileContainer::supportsPerceptual() const 0369 { 0370 return d->isPerceptualCLUT; 0371 } 0372 bool LcmsColorProfileContainer::supportsSaturation() const 0373 { 0374 return d->isSaturationCLUT; 0375 } 0376 bool LcmsColorProfileContainer::supportsAbsolute() const 0377 { 0378 return d->isAbsoluteCLUT;//LCMS2 doesn't convert matrix shapers via absolute intent, because of V4 workflow. 0379 } 0380 bool LcmsColorProfileContainer::supportsRelative() const 0381 { 0382 if (d->isRelativeCLUT || d->isMatrixShaper){ 0383 return true; 0384 } 0385 return false; 0386 } 0387 bool LcmsColorProfileContainer::hasColorants() const 0388 { 0389 return d->hasColorants; 0390 } 0391 bool LcmsColorProfileContainer::hasTRC() const 0392 { 0393 return *d->hasTRC; 0394 } 0395 bool LcmsColorProfileContainer::isLinear() const 0396 { 0397 return *d->isLinear; 0398 } 0399 QVector <double> LcmsColorProfileContainer::getColorantsXYZ() const 0400 { 0401 QVector <double> colorants(9); 0402 colorants[0] = d->colorants.Red.X; 0403 colorants[1] = d->colorants.Red.Y; 0404 colorants[2] = d->colorants.Red.Z; 0405 colorants[3] = d->colorants.Green.X; 0406 colorants[4] = d->colorants.Green.Y; 0407 colorants[5] = d->colorants.Green.Z; 0408 colorants[6] = d->colorants.Blue.X; 0409 colorants[7] = d->colorants.Blue.Y; 0410 colorants[8] = d->colorants.Blue.Z; 0411 return colorants; 0412 } 0413 0414 QVector <double> LcmsColorProfileContainer::getColorantsxyY() const 0415 { 0416 cmsCIEXYZ temp1; 0417 cmsCIExyY temp2; 0418 QVector <double> colorants(9); 0419 0420 temp1.X = d->colorants.Red.X; 0421 temp1.Y = d->colorants.Red.Y; 0422 temp1.Z = d->colorants.Red.Z; 0423 cmsXYZ2xyY(&temp2, &temp1); 0424 colorants[0] = temp2.x; 0425 colorants[1] = temp2.y; 0426 colorants[2] = temp2.Y; 0427 0428 temp1.X = d->colorants.Green.X; 0429 temp1.Y = d->colorants.Green.Y; 0430 temp1.Z = d->colorants.Green.Z; 0431 cmsXYZ2xyY(&temp2, &temp1); 0432 colorants[3] = temp2.x; 0433 colorants[4] = temp2.y; 0434 colorants[5] = temp2.Y; 0435 0436 temp1.X = d->colorants.Blue.X; 0437 temp1.Y = d->colorants.Blue.Y; 0438 temp1.Z = d->colorants.Blue.Z; 0439 cmsXYZ2xyY(&temp2, &temp1); 0440 colorants[6] = temp2.x; 0441 colorants[7] = temp2.y; 0442 colorants[8] = temp2.Y; 0443 0444 return colorants; 0445 } 0446 0447 QVector <double> LcmsColorProfileContainer::getWhitePointXYZ() const 0448 { 0449 QVector <double> tempWhitePoint(3); 0450 0451 tempWhitePoint[0] = d->mediaWhitePoint.X; 0452 tempWhitePoint[1] = d->mediaWhitePoint.Y; 0453 tempWhitePoint[2] = d->mediaWhitePoint.Z; 0454 0455 return tempWhitePoint; 0456 } 0457 0458 QVector <double> LcmsColorProfileContainer::getWhitePointxyY() const 0459 { 0460 QVector <double> tempWhitePoint(3); 0461 tempWhitePoint[0] = d->whitePoint.x; 0462 tempWhitePoint[1] = d->whitePoint.y; 0463 tempWhitePoint[2] = d->whitePoint.Y; 0464 return tempWhitePoint; 0465 } 0466 0467 QVector <double> LcmsColorProfileContainer::getEstimatedTRC() const 0468 { 0469 QVector <double> TRCtriplet(3); 0470 if (d->hasColorants) { 0471 if (cmsIsToneCurveLinear(d->redTRC)) { 0472 TRCtriplet[0] = 1.0; 0473 } else { 0474 TRCtriplet[0] = cmsEstimateGamma(d->redTRC, 0.01); 0475 } 0476 if (cmsIsToneCurveLinear(d->greenTRC)) { 0477 TRCtriplet[1] = 1.0; 0478 } else { 0479 TRCtriplet[1] = cmsEstimateGamma(d->greenTRC, 0.01); 0480 } 0481 if (cmsIsToneCurveLinear(d->blueTRC)) { 0482 TRCtriplet[2] = 1.0; 0483 } else { 0484 TRCtriplet[2] = cmsEstimateGamma(d->blueTRC, 0.01); 0485 } 0486 0487 } else { 0488 if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) { 0489 if (cmsIsToneCurveLinear(d->grayTRC)) { 0490 TRCtriplet.fill(1.0); 0491 } else { 0492 TRCtriplet.fill(cmsEstimateGamma(d->grayTRC, 0.01)); 0493 } 0494 } else { 0495 TRCtriplet.fill(1.0); 0496 } 0497 } 0498 return TRCtriplet; 0499 } 0500 0501 void LcmsColorProfileContainer::LinearizeFloatValue(QVector <double> & Value) const 0502 { 0503 if (d->hasColorants) { 0504 if (!cmsIsToneCurveLinear(d->redTRC)) { 0505 Value[0] = cmsEvalToneCurveFloat(d->redTRC, Value[0]); 0506 } 0507 if (!cmsIsToneCurveLinear(d->greenTRC)) { 0508 Value[1] = cmsEvalToneCurveFloat(d->greenTRC, Value[1]); 0509 } 0510 if (!cmsIsToneCurveLinear(d->blueTRC)) { 0511 Value[2] = cmsEvalToneCurveFloat(d->blueTRC, Value[2]); 0512 } 0513 0514 } else { 0515 if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) { 0516 Value[0] = cmsEvalToneCurveFloat(d->grayTRC, Value[0]); 0517 } 0518 } 0519 } 0520 0521 void LcmsColorProfileContainer::DelinearizeFloatValue(QVector <double> & Value) const 0522 { 0523 if (d->hasColorants) { 0524 if (!cmsIsToneCurveLinear(d->redTRC)) { 0525 Value[0] = cmsEvalToneCurveFloat(*d->redTRCReverse, Value[0]); 0526 } 0527 if (!cmsIsToneCurveLinear(d->greenTRC)) { 0528 Value[1] = cmsEvalToneCurveFloat(*d->greenTRCReverse, Value[1]); 0529 } 0530 if (!cmsIsToneCurveLinear(d->blueTRC)) { 0531 Value[2] = cmsEvalToneCurveFloat(*d->blueTRCReverse, Value[2]); 0532 } 0533 0534 } else { 0535 if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) { 0536 Value[0] = cmsEvalToneCurveFloat(*d->grayTRCReverse, Value[0]); 0537 } 0538 } 0539 } 0540 0541 void LcmsColorProfileContainer::LinearizeFloatValueFast(QVector <double> & Value) const 0542 { 0543 const qreal scale = 65535.0; 0544 const qreal invScale = 1.0 / scale; 0545 0546 if (d->hasColorants) { 0547 //we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone. 0548 0549 if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) { 0550 quint16 newValue = cmsEvalToneCurve16(d->redTRC, Value[0] * scale); 0551 Value[0] = newValue * invScale; 0552 } 0553 if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) { 0554 quint16 newValue = cmsEvalToneCurve16(d->greenTRC, Value[1] * scale); 0555 Value[1] = newValue * invScale; 0556 } 0557 if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) { 0558 quint16 newValue = cmsEvalToneCurve16(d->blueTRC, Value[2] * scale); 0559 Value[2] = newValue * invScale; 0560 } 0561 } else { 0562 if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) { 0563 quint16 newValue = cmsEvalToneCurve16(d->grayTRC, Value[0] * scale); 0564 Value[0] = newValue * invScale; 0565 } 0566 } 0567 } 0568 void LcmsColorProfileContainer::DelinearizeFloatValueFast(QVector <double> & Value) const 0569 { 0570 const qreal scale = 65535.0; 0571 const qreal invScale = 1.0 / scale; 0572 0573 if (d->hasColorants) { 0574 //we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone. 0575 0576 if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) { 0577 quint16 newValue = cmsEvalToneCurve16(*d->redTRCReverse, Value[0] * scale); 0578 Value[0] = newValue * invScale; 0579 } 0580 if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) { 0581 quint16 newValue = cmsEvalToneCurve16(*d->greenTRCReverse, Value[1] * scale); 0582 Value[1] = newValue * invScale; 0583 } 0584 if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) { 0585 quint16 newValue = cmsEvalToneCurve16(*d->blueTRCReverse, Value[2] * scale); 0586 Value[2] = newValue * invScale; 0587 } 0588 } else { 0589 if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) { 0590 quint16 newValue = cmsEvalToneCurve16(*d->grayTRCReverse, Value[0] * scale); 0591 Value[0] = newValue * invScale; 0592 } 0593 } 0594 } 0595 0596 QString LcmsColorProfileContainer::name() const 0597 { 0598 return d->name; 0599 } 0600 0601 QString LcmsColorProfileContainer::info() const 0602 { 0603 return d->productDescription; 0604 } 0605 0606 QByteArray LcmsColorProfileContainer::getProfileUniqueId() const 0607 { 0608 if (d->uniqueId.isEmpty() && d->profile) { 0609 QByteArray id(sizeof(cmsProfileID), 0); 0610 cmsGetHeaderProfileID(d->profile, (quint8*)id.data()); 0611 0612 bool isNull = std::all_of(id.constBegin(), 0613 id.constEnd(), 0614 [](char c) {return c == 0;}); 0615 if (isNull) { 0616 if (cmsMD5computeID(d->profile)) { 0617 cmsGetHeaderProfileID(d->profile, (quint8*)id.data()); 0618 isNull = false; 0619 } 0620 } 0621 0622 if (!isNull) { 0623 d->uniqueId = id; 0624 } 0625 } 0626 0627 return d->uniqueId; 0628 } 0629 0630 bool LcmsColorProfileContainer::compareTRC(TransferCharacteristics characteristics, float error) const 0631 { 0632 if (!*d->hasTRC) { 0633 return false; 0634 } 0635 0636 std::array<cmsFloat32Number, 2> calcValues{}; 0637 0638 cmsToneCurve *mainCurve = [&]() { 0639 if (d->hasColorants) { 0640 return d->redTRC; 0641 } 0642 return d->grayTRC; 0643 }(); 0644 0645 cmsToneCurve *compareCurve = transferFunction(characteristics); 0646 0647 // Number of sweep samples across the curve 0648 for (uint32_t i = 0; i < 32; i++) { 0649 const float step = float(i) / 31.0f; 0650 calcValues[0] = cmsEvalToneCurveFloat(mainCurve, step); 0651 calcValues[1] = cmsEvalToneCurveFloat(compareCurve, step); 0652 if (std::fabs(calcValues[0] - calcValues[1]) >= error) { 0653 return false; 0654 } 0655 } 0656 0657 return true; 0658 } 0659 0660 cmsToneCurve *LcmsColorProfileContainer::transferFunction(TransferCharacteristics transferFunction) 0661 { 0662 cmsToneCurve *mainCurve; 0663 0664 // Values courtesy of Elle Stone 0665 cmsFloat64Number srgb_parameters[5] = 0666 { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 }; 0667 cmsFloat64Number rec709_parameters[5] = 0668 { 1.0 / 0.45, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 }; 0669 0670 // The following is basically a precise version of rec709. 0671 cmsFloat64Number rec202012bit_parameters[5] = 0672 { 1.0 / 0.45, 1.0 / 1.0993, 0.0993 / 1.0993, 1.0 / 4.5, 0.0812 }; 0673 0674 cmsFloat64Number SMPTE_240M_parameters[5] = 0675 { 1.0 / 0.45, 1.0 / 1.1115, 0.1115 / 1.1115, 1.0 / 4.0, 0.0913 }; 0676 0677 cmsFloat64Number prophoto_parameters[5] = 0678 { 1.8, 1.0, 0, 1.0 / 16, (16.0/512) }; 0679 0680 cmsFloat64Number log_100[5] = {1.0, 10, 2.0, -2.0, 0.0}; 0681 cmsFloat64Number log_100_sqrt[5] = {1.0, 10, 2.5, -2.5, 0.0}; 0682 0683 cmsFloat64Number labl_parameters[5] = {3.0, 0.862076, 0.137924, 0.110703, 0.080002}; 0684 0685 switch (transferFunction) { 0686 case TRC_IEC_61966_2_4: 0687 // Not possible in ICC due to lack of a*pow(bX+c,y) construct. 0688 case TRC_ITU_R_BT_1361: 0689 // This is not possible in ICC due to lack of a*pow(bX+c,y) construct. 0690 qWarning() << "Neither IEC 61966 2-4 nor Bt. 1361 are supported, returning a rec 709 curve."; 0691 Q_FALLTHROUGH(); 0692 case TRC_ITU_R_BT_709_5: 0693 case TRC_ITU_R_BT_601_6: 0694 case TRC_ITU_R_BT_2020_2_10bit: 0695 mainCurve = cmsBuildParametricToneCurve(NULL, 4, rec709_parameters); 0696 break; 0697 case TRC_ITU_R_BT_2020_2_12bit: 0698 mainCurve = cmsBuildParametricToneCurve(NULL, 4, rec202012bit_parameters); 0699 break; 0700 case TRC_ITU_R_BT_470_6_SYSTEM_M: 0701 mainCurve = cmsBuildGamma(NULL, 2.2); 0702 break; 0703 case TRC_ITU_R_BT_470_6_SYSTEM_B_G: 0704 mainCurve = cmsBuildGamma(NULL, 2.8); 0705 break; 0706 case TRC_SMPTE_240M: 0707 mainCurve = cmsBuildParametricToneCurve(NULL, 4, SMPTE_240M_parameters); 0708 break; 0709 case TRC_IEC_61966_2_1: 0710 mainCurve = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters); 0711 break; 0712 case TRC_LOGARITHMIC_100: 0713 mainCurve = cmsBuildParametricToneCurve(NULL, 8, log_100); 0714 break; 0715 case TRC_LOGARITHMIC_100_sqrt10: 0716 mainCurve = cmsBuildParametricToneCurve(NULL, 8, log_100_sqrt); 0717 break; 0718 case TRC_A98: 0719 // gamma 563/256 0720 mainCurve = cmsBuildGamma(NULL, 563.0 / 256.0); 0721 break; 0722 case TRC_PROPHOTO: 0723 mainCurve = cmsBuildParametricToneCurve(NULL, 4, prophoto_parameters); 0724 break; 0725 case TRC_GAMMA_1_8: 0726 mainCurve = cmsBuildGamma(NULL, 1.8); 0727 break; 0728 case TRC_GAMMA_2_4: 0729 mainCurve = cmsBuildGamma(NULL, 2.4); 0730 break; 0731 case TRC_LAB_L: 0732 mainCurve = cmsBuildParametricToneCurve(NULL, 4, labl_parameters); 0733 break; 0734 case TRC_SMPTE_ST_428_1: 0735 // Requires an a*X^y construction, not possible. 0736 case TRC_ITU_R_BT_2100_0_PQ: 0737 // Perceptual Quantizer 0738 case TRC_ITU_R_BT_2100_0_HLG: 0739 // Hybrid log gamma. 0740 qWarning() << "Cannot generate an icc profile with this transfer function, will generate a linear profile"; 0741 Q_FALLTHROUGH(); 0742 case TRC_LINEAR: 0743 default: 0744 mainCurve = cmsBuildGamma(NULL, 1.0); 0745 break; 0746 } 0747 0748 return mainCurve; 0749 }