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 }