File indexing completed on 2025-02-23 04:10:59

0001 /*
0002  *  SPDX-FileCopyrightText: 2021 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "TestProfileGeneration.h"
0008 
0009 #include <QTest>
0010 #include <cmath>
0011 #include <lcms2.h>
0012 
0013 #include <KoColorConversions.h>
0014 #include <KoColorTransferFunctions.h>
0015 #include <kis_debug.h>
0016 #include <testpigment.h>
0017 
0018 void TestProfileGeneration::testTransferFunctions()
0019 {
0020     /*
0021      *  Generate every possible transfer function and check their validity.
0022      */
0023 
0024     cmsFloat64Number srgb_parameters[5] =
0025     { 2.4, 1.0 / 1.055,  0.055 / 1.055, 1.0 / 12.92, 0.04045 };
0026     cmsFloat64Number rec709_parameters[5] =
0027     { 1.0 / 0.45, 1.0 / 1.099,  0.099 / 1.099,  1.0 / 4.5, 0.081 };
0028 
0029     cmsFloat64Number SMPTE_240M_parameters[5] =
0030     { 1.0 / 0.45, 1.0 / 1.1115,  0.1115 / 1.1115,  1.0 / 4.0, 0.0913 };
0031 
0032     cmsFloat64Number prophoto_parameters[5] =
0033     { 1.8, 1.0,  0, 1.0 / 16, (16/512) };
0034 
0035     cmsFloat64Number log_100[5] = {1.0, 10, 2.0, -2.0, 0.0};
0036     cmsFloat64Number log_100_sqrt[5] = {1.0, 10, 2.5, -2.5, 0.0};
0037 
0038 
0039     QVector<cmsFloat32Number> inputValues = {0};
0040     QVector<double> inputValuesRaw = {0};
0041     for (int i = 0; i < 60; i++) {
0042         //inputValues.insert(0, -.01 * (i+1) );
0043         inputValues.append(    .01 * (i+1) );
0044         inputValuesRaw.append( .01 * (i+1) );
0045     }
0046 
0047     QVector<double> col;
0048     cmsToneCurve *curve  = cmsBuildParametricToneCurve(NULL, 4, rec709_parameters);
0049 
0050     for (cmsFloat32Number value : inputValues) {
0051         cmsFloat32Number cValue = value;
0052 
0053         /*
0054          * Rec 709
0055          * for 1  >=  Lc  >=  β
0056          *  V = α * Lc^0.45 − ( α − 1 )
0057          * for β  >  Lc  >=  0
0058          * V = 4.500 * Lc
0059          */
0060 
0061         if (value > 0.018){
0062             cValue = 1.099 * powf(value, 0.45) - (.099) ;
0063         } else if (value < 0.018 && value > 0.0){
0064             cValue = 4.5 * value;
0065         } else {
0066             cValue = 0.0;
0067         }
0068 
0069 
0070 
0071         /*
0072          * double lValue;
0073         if (cValue > 0.081){
0074             lValue = powf((.099+cValue)*1/1.099, 1/ 0.45);
0075         } else {
0076             lValue = cValue * 1/4.5;
0077         }
0078         */
0079         cmsFloat32Number lValue = cmsEvalToneCurveFloat(curve, cValue);
0080         QVERIFY2(fabs(lValue - value) < 0.000001, QString("Values don't match for rec 709: %1 %2").arg(value).arg(lValue).toLatin1());
0081     }
0082 
0083     curve = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
0084 
0085     for (cmsFloat32Number value : inputValues) {
0086         cmsFloat32Number cValue = value;
0087 
0088         /*
0089          * sRGB
0090          * for 1  >  Lc  >=  β
0091          *  V = α * Lc( 1÷2.4 ) − ( α − 1 )
0092          * for β  >  Lc  >=  0
0093          *  V = 12.92 * Lc
0094          */
0095 
0096         if (value > 0.0031308){
0097             cValue = 1.055 * powf(value, 1/2.4) - (.055) ;
0098         } else if (value < 0.0031308 && value > 0.0){
0099             cValue = 12.92 * value;
0100         } else {
0101             cValue = 0.0;
0102         }
0103 
0104         /*
0105         double lValue;
0106         if (cValue > 0.04045){
0107             lValue = powf((cValue)*1/1.055 + (.055/1.055), 2.4);
0108         } else {
0109             lValue = cValue * 1/12.92;
0110         }
0111         */
0112 
0113         cmsFloat32Number lValue = cmsEvalToneCurveFloat(curve, cValue);
0114         QVERIFY2(fabs(lValue - value) < 0.000001, QString("Values don't match for sRGB: %1 %2").arg(value).arg(lValue).toLatin1());
0115     }
0116 
0117     curve = cmsBuildParametricToneCurve(NULL, 4, SMPTE_240M_parameters);
0118 
0119     for (cmsFloat32Number value : inputValues) {
0120         cmsFloat32Number cValue = value;
0121 
0122         /*
0123          * SMPTE 240M
0124          * for 1  >=  Lc  >=  β
0125          *  V = α * Lc^0.45 − ( α − 1 )
0126          * for β  >  Lc  >=  0
0127          * V = 4.0 * Lc
0128          */
0129 
0130         if (value > 0.0228){
0131             cValue = 1.1115 * powf(value, 0.45) - (.1115) ;
0132         } else if (value < 0.0228 && value > 0.0){
0133             cValue = 4.0 * value;
0134         } else {
0135             cValue = 0.0;
0136         }
0137 
0138         /*
0139         double lValue;
0140         if (cValue > 0.0913){
0141             lValue = powf((.1115+cValue)*1/1.1115, 1/ 0.45);
0142         } else {
0143             lValue = cValue * 1/4.0;
0144         }
0145         */
0146 
0147         cmsFloat32Number lValue = cmsEvalToneCurveFloat(curve, cValue);
0148         QVERIFY2(fabs(lValue - value) < 0.000001, QString("Values don't match for SMPTE 240M: %1 %2").arg(value).arg(lValue).toLatin1());
0149     }
0150 
0151     curve = cmsBuildParametricToneCurve(NULL, 4, prophoto_parameters);
0152 
0153     for (cmsFloat32Number value : inputValues) {
0154         cmsFloat32Number cValue = value;
0155 
0156         // Prophoto RGB according to css 4 specs.
0157 
0158         if (value > 1.0/512.0){
0159             cValue = powf(value, 1/1.8) ;
0160         } else if (value < 0.0228 && value > 0.0){
0161             cValue = 16.0 * value;
0162         }
0163 
0164         /*
0165         double lValue;
0166         if (cValue > 16.0/512.0){
0167             lValue = powf(cValue, 1.8);
0168         } else {
0169             lValue = cValue * 1/16.0;
0170         }
0171         */
0172 
0173         cmsFloat32Number lValue = cmsEvalToneCurveFloat(curve, cValue);
0174         QVERIFY2(fabs(lValue - value) < 0.000001, QString("Values don't match for ProPhoto: %1 %2").arg(value).arg(lValue).toLatin1());
0175     }
0176 
0177     for (double value : inputValuesRaw) {
0178         double cValue = value;
0179 
0180         /* IEC 61966-2-4 ...
0181          * for Lc between 1 and  β
0182          *  V = α * Lc^0.45 − ( α − 1)
0183          * for between β and - β
0184          *  V = 4.5 * Lc
0185          * for below -β
0186          *  V = −α * ( −Lc )^0.45 + ( α − 1)
0187          *
0188          * reverse = ((1.0 / 1.099)* V + (-0.099 / 1.099) )^(1.0/0.45) + c
0189          */
0190 
0191         if (value > 0.018){
0192             cValue = 1.099 * powf(value, 0.45) - (.099) ;
0193         } else if (value < 0.018 && value > -0.018){
0194             cValue = 4.5 * value;
0195         } else {
0196             cValue = -1.099 * powf(-value, 0.45) - (-.099) ;
0197         }
0198 
0199 
0200         double lValue;
0201         if (cValue > 0.018){
0202             lValue = powf((.099+cValue)*1/1.099, 1/ 0.45);
0203         } else if (cValue < -0.018) {
0204              lValue = powf( (cValue/-1.099) + (-.099/-1.099), 1/ 0.45) * -1;
0205         } else {
0206             lValue = cValue * 1/4.5;
0207         }
0208 
0209         // Also not possible in iccv4.
0210         QVERIFY2(fabs(lValue - value) < 0.001, QString("Values don't match for IEC 61966-2-4: %1 %2").arg(value).arg(lValue).toLatin1());
0211     }
0212 
0213     for (double value : inputValuesRaw) {
0214         double cValue = value;
0215 
0216         /*
0217          * TRC_ITU_R_BT_1361
0218          * Extended historical gamut system.
0219          *
0220          * for 1.33 > Lc >= β (0.018)
0221          *  V = α * Lc^0.45 − ( α − 1 )
0222          * for β > Lc >= −γ (-0.0045)
0223          *  V = 4.500 * Lc
0224          * for −γ >= Lc >= −0.25
0225          *  V = −( α * ( −4 * Lc )^0.45 − ( α − 1 ) ) ÷ 4
0226          *
0227          * reverse:
0228          * ( (1.0 / 1.099) * Lc + (0.099 / 1.099) ) ^ (1/0.45)
0229          * Lc* (1/4.5)
0230          * -(4/1.099)*(Lc*-.25)^(1/0.45)???
0231          */
0232 
0233 
0234         if (value > 0.018){
0235             cValue = 1.099 * powf(value, 0.45) - (.099) ;
0236         } else if (value < 0.018 && value > -0.0045){
0237             cValue = 4.5 * value;
0238         } else {
0239             cValue = -(1.099 * powf(-4 * value, 0.45) - (.099)) * 0.25 ;
0240         }
0241 
0242         double lValue;
0243         if (cValue > 0.018){
0244             lValue = powf((.099/1.099)+(cValue*1/1.099), 1/ 0.45);
0245         } else if (cValue < -0.0045) {
0246             lValue = powf( (cValue * 4 / -1.099 ) - (0.099 / -1.099), 1/ 0.45) * -.25;
0247         } else {
0248             lValue = cValue * 1/4.5;
0249         }
0250         // This is not possible in ICC v4.
0251 
0252         QVERIFY2(fabs(lValue - value) < 0.001, QString("Values don't match for bt. 1361: %1 %2").arg(value).arg(lValue).toLatin1());
0253     }
0254 
0255     curve = cmsBuildParametricToneCurve(NULL, 8, log_100);
0256 
0257     for (cmsFloat32Number value : inputValues) {
0258         cmsFloat32Number cValue = value;
0259 
0260         /*
0261          * Logarithmic 100
0262          * for Lc between 1 and 0.01:
0263          *  V = 1.0 + Log10( Lc ) ÷ 2
0264          * for Lc below 0.01:
0265          *  V = 0.0
0266          */
0267 
0268         if (value > 0.01){
0269             cValue = 1.0+log10(value) *.5 ;
0270         } else{
0271             cValue = 0.0;
0272         }
0273 
0274         /*
0275         double lValue = powf(10.0, (cValue - 1.0) * 2 );
0276         if (cValue == 0) {
0277             lValue = 0;
0278         }
0279         */
0280         cmsFloat32Number lValue = cmsEvalToneCurveFloat(curve, cValue);
0281 
0282         if (value > cmsFloat32Number(0.01)) {
0283             QVERIFY2(fabs(lValue - value) < 0.000001, QString("Values don't match for log 100: %1 %2").arg(value).arg(lValue).toLatin1());
0284         }
0285     }
0286 
0287     curve = cmsBuildParametricToneCurve(NULL, 8, log_100_sqrt);
0288 
0289     for (cmsFloat32Number value : inputValues) {
0290         cmsFloat32Number cValue = value;
0291 
0292         /*
0293          * logarithmic_100_sqrt10
0294          * for Lc between 1 and Sqrt( 10 ) ÷ 1000
0295          *  V = 1.0 + Log10( Lc ) ÷ 2.5
0296          * for Lc below Sqrt( 10 ) ÷ 1000
0297          *  V = 0.0
0298          */
0299 
0300         if (value > (sqrt(10)/1000)){
0301             cValue = 1.0+log10(value) * (1/2.5) ;
0302         } else{
0303             cValue = 0.0;
0304         }
0305 
0306         /*
0307 
0308         double lValue = powf(10.0, (cValue - 1.0) * 2.5 );
0309         if (cValue == 0) {
0310             lValue = 0;
0311         }
0312 
0313         */
0314 
0315         cmsFloat32Number lValue = cmsEvalToneCurveFloat(curve, cValue);
0316         if (value > cmsFloat32Number(sqrt(10)/1000)) {
0317             QVERIFY2(fabs(lValue - value) < 0.000001, QString("Values don't match for log 100 sqrt: %1 %2").arg(value).arg(lValue).toLatin1());
0318         }
0319 
0320     }
0321 
0322 
0323     for (double value : inputValuesRaw) {
0324         double cValue = value;
0325         /*
0326          * SMPTE_ST_428_1
0327          * V= (48 * Lo / 52.37)^(1/2.6)
0328          *
0329          */
0330 
0331         //cValue = powf(48 * value/ 52.37, (1/2.6)) ;
0332 
0333         cValue = applySMPTE_ST_428Curve(value);
0334 
0335         double lValue = (52.37/48.0 * powf( cValue , 2.6 ));
0336 
0337         //Not possible in icc v4
0338 
0339         QVERIFY2(fabs(lValue - value) < 0.000001, QString("Values don't match for SMPTE ST 428 1: %1 %2").arg(value).arg(lValue).toLatin1());
0340 
0341     }
0342 
0343     for (double value : inputValuesRaw) {
0344         double cValue = value;
0345         /*
0346          * HLG
0347          * for 1  >=  Lc > 1 ÷ 12
0348          *  V= a * Ln( 12 * Lc − b ) + c
0349          * for 1 ÷ 12  >=  Lc  >=  0
0350          *  V = Sqrt( 3 ) * Lc^0.5
0351          *
0352          * where...
0353          * a = 0.17883277
0354          * b = 0.28466892
0355          * c = 0.55991073
0356          *
0357          */
0358 
0359         double a = 0.17883277;
0360         double b = 0.28466892;
0361         double c = 0.55991073;
0362 
0363 
0364         if (value > 1.0/12.0) {
0365             cValue = a*log(12*value-b) + c;
0366         } else {
0367             cValue = sqrt(3) * powf(value, 0.5);
0368         }
0369 
0370         double cValue2 = applyHLGCurve(value);
0371 
0372 
0373         double lValue = (exp(((cValue2 - c) / a)) + b) / 12.0;
0374         if (cValue2 <= 0.5) {
0375             lValue = powf(cValue2, 2.0) / 3.0;
0376         }
0377 
0378         double lValue2 = removeHLGCurve(cValue);
0379 
0380         //Not possible in icc v4
0381         QVERIFY2(fabs(lValue - value) < 0.000001, QString("Values don't match for HLG: %1 %2").arg(value).arg(lValue).toLatin1());
0382         QVERIFY2(fabs(lValue2 - value) < 0.000001, QString("Values don't match for HLG, 2: %1 %2").arg(value).arg(lValue2).toLatin1());
0383 
0384     }
0385 
0386     cmsFreeToneCurve(curve);
0387 
0388 }
0389 
0390 KISTEST_MAIN(TestProfileGeneration)