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)