File indexing completed on 2024-10-20 04:17:36
0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com> 0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT 0003 0004 // First included header is the public header of the class we are testing; 0005 // this forces the header to be self-contained. 0006 #include "absolutecolor.h" 0007 0008 #include "genericcolor.h" 0009 #include "helperconversion.h" 0010 #include "helpermath.h" 0011 #include <algorithm> 0012 #include <lcms2.h> 0013 #include <optional> 0014 #include <qgenericmatrix.h> 0015 #include <qglobal.h> 0016 #include <qhash.h> 0017 #include <qmetatype.h> 0018 #include <qobject.h> 0019 #include <qtest.h> 0020 #include <qtestcase.h> 0021 #include <qtestdata.h> 0022 0023 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0024 #include <qtmetamacros.h> 0025 #else 0026 #include <qobjectdefs.h> 0027 #include <qstring.h> 0028 #endif 0029 0030 Q_DECLARE_METATYPE(cmsCIELab) 0031 0032 namespace PerceptualColor 0033 { 0034 class TestAbsoluteColor : public QObject 0035 { 0036 Q_OBJECT 0037 0038 public: 0039 explicit TestAbsoluteColor(QObject *parent = nullptr) 0040 : QObject(parent) 0041 { 0042 } 0043 0044 private: 0045 void generateDataXyzd65Oklab() 0046 { 0047 qRegisterMetaType<Trio>(); 0048 QTest::addColumn<double>("x"); 0049 QTest::addColumn<double>("y"); 0050 QTest::addColumn<double>("z"); 0051 QTest::addColumn<Trio>("oklab"); 0052 0053 // The following reference values come from from the original paper: 0054 // https://bottosson.github.io/posts/oklab/#table-of-example-xyz-and-oklab-pairs 0055 0056 constexpr double oklabHighXYZ[] = // 0057 {1.000, 0.000, 0.000}; 0058 QTest::newRow("highXYZ 0.950 1.000 1.089") // 0059 << 0.950 << 1.000 << 1.089 // 0060 << Trio(oklabHighXYZ); 0061 0062 constexpr double oklabHighX[] = // 0063 {0.450, 1.236, -0.019}; 0064 QTest::newRow("highX 1.000 0.000 0.000") // 0065 << 1.000 << 0.000 << 0.000 // 0066 << Trio(oklabHighX); 0067 0068 constexpr double oklabHighY[] = // 0069 {0.922, -0.671, 0.263}; 0070 QTest::newRow("highY 0.000 1.000 0.000") // 0071 << 0.000 << 1.000 << 0.000 // 0072 << Trio(oklabHighY); 0073 0074 constexpr double oklabHighZ[] = // 0075 {0.153, -1.415, -0.449}; 0076 QTest::newRow("highZ 0.000 0.000 1.000") // 0077 << 0.000 << 0.000 << 1.000 // 0078 << Trio(oklabHighZ); 0079 0080 // The following reference values have been calculated with the 0081 // online tool https://colorjs.io/apps/convert/ 0082 0083 constexpr double oklabWhite[] = // 0084 {0.9999999934735462, 8.095285553e-11, 3.727390762709e-8}; 0085 QTest::newRow("white 0.9504559270516717 1. 1.0890577507598784") // 0086 << 0.9504559270516717 << 1. << 1.0890577507598784 // 0087 << Trio(oklabWhite); 0088 0089 constexpr double oklabRed[] = // 0090 {0.6279553606145516, 0.22486306106597398, 0.1258462985307351}; 0091 QTest::newRow("red 0.41239079926595934 0.21263900587151027 0.01933081871559182") // 0092 << 0.41239079926595934 << 0.21263900587151027 << 0.01933081871559182 // 0093 << Trio(oklabRed); 0094 0095 constexpr double oklabGreen[] = // 0096 {0.519751827794842, -0.14030232755311015, 0.10767589774360209}; 0097 QTest::newRow("green 0.0771883343323022 0.1543766686646044 0.02572944477743406") // 0098 << 0.0771883343323022 << 0.1543766686646044 << 0.02572944477743406 // 0099 << Trio(oklabGreen); 0100 0101 constexpr double oklabBlue[] = // 0102 {0.4520137183853429, -0.03245698416876397, -0.3115281476783752}; 0103 QTest::newRow("blue 0.1804807884018343 0.07219231536073371 0.9505321522496607") // 0104 << 0.1804807884018343 << 0.07219231536073371 << 0.9505321522496607 // 0105 << Trio(oklabBlue); 0106 0107 constexpr double oklabCyan[] = // 0108 {0.9053992300557675, -0.14944393961066077, -0.03939815774426181}; 0109 QTest::newRow("cyan 0.5380651277857122 0.7873609941284897 1.0697269320442866") // 0110 << 0.5380651277857122 << 0.7873609941284897 << 1.0697269320442866 // 0111 << Trio(oklabCyan); 0112 0113 constexpr double oklabMagenta[] = // 0114 {0.7016738558717924, 0.27456629431932855, -0.16915605926294264}; 0115 QTest::newRow("magenta 0.5928715876677937 0.284831321232244 0.9698629709652525") // 0116 << 0.5928715876677937 << 0.284831321232244 << 0.9698629709652525 // 0117 << Trio(oklabMagenta); 0118 0119 constexpr double oklabYellow[] = // 0120 {0.9679827203267873, -0.07136908036816808, 0.19856975465179516}; 0121 QTest::newRow("yellow 0.7699751386498374 0.9278076846392663 0.13852559851021778") // 0122 << 0.7699751386498374 << 0.9278076846392663 << 0.13852559851021778 // 0123 << Trio(oklabYellow); 0124 0125 constexpr double oklabBlack[] = // 0126 {0., 0., 0.}; 0127 QTest::newRow("black 0. 0. 0.") // 0128 << 0. << 0. << 0. // 0129 << Trio(oklabBlack); 0130 0131 constexpr double oklabGray[] = // 0132 {0.5998708017071177, 4.856132163e-11, 2.235952889507e-8}; 0133 QTest::newRow("gray 0.2051658917495936 0.21586050011389926 0.23508455073194565") // 0134 << 0.2051658917495936 << 0.21586050011389926 << 0.23508455073194565 // 0135 << Trio(oklabGray); 0136 } 0137 0138 void generateDataCielabd50Oklab() 0139 { 0140 qRegisterMetaType<Trio>(); 0141 qRegisterMetaType<cmsCIELab>(); 0142 QTest::addColumn<cmsCIELab>("cmscielab"); 0143 QTest::addColumn<Trio>("oklab"); 0144 0145 // The following reference values have been calculated with the 0146 // online tool https://colorjs.io/apps/convert/ 0147 0148 constexpr double oklabSpecialWhite[] = // 0149 {1.0000000010492212, -1.0775085046432764e-8, 5.03845311028428e-8}; 0150 // TODO xxx This is out-of-bound! What is the expected behaviour??? 0151 QTest::newRow("special white 100., 0., 0.") // 0152 << cmsCIELab({100., 0., 0.}) // 0153 << Trio(oklabSpecialWhite); 0154 0155 constexpr double oklabWhite[] = // 0156 {1.000000009791752, -3.3637913787742946e-8, 6.836016341882356e-8}; 0157 // TODO xxx This is out-of-bound! What is the expected behaviour??? 0158 QTest::newRow("white 100.00000139649632, -0.000007807961277528364, 0.000006766250648659877") // 0159 << cmsCIELab({100.00000139649632, -0.000007807961277528364, 0.000006766250648659877}) // 0160 << Trio(oklabWhite); 0161 0162 constexpr double oklabRed[] = // 0163 {0.627955380062011, 0.22486300104638587, 0.1258463407318262}; 0164 QTest::newRow("red 54.29054294696968 80.80492033462421 69.89098825896275") // 0165 << cmsCIELab({54.29054294696968, 80.80492033462421, 69.89098825896275}) // 0166 << Trio(oklabRed); 0167 0168 constexpr double oklabGreen[] = // 0169 {0.5197518404266431, -0.14030239549323664, 0.10767592658888475}; 0170 QTest::newRow("green 46.27770902748027 -47.55240796497723 48.58629466423457") // 0171 << cmsCIELab({46.27770902748027, -47.55240796497723, 48.58629466423457}) // 0172 << Trio(oklabGreen); 0173 0174 constexpr double oklabBlue[] = // 0175 {0.4520136952286447, -0.03245661282391282, -0.3115281896078159}; 0176 QTest::newRow("blue 29.56829715344471 68.28740665215547 -112.02971798617645") // 0177 << cmsCIELab({29.56829715344471, 68.28740665215547, -112.02971798617645}) // 0178 << Trio(oklabBlue); 0179 0180 constexpr double oklabCyan[] = // 0181 {0.9053992412363845, -0.14944395453880494, -0.03939813576103679}; 0182 QTest::newRow("cyan 90.66601315791455 -50.65651077286893 -14.961666625736525") // 0183 << cmsCIELab({90.66601315791455, -50.65651077286893, -14.961666625736525}) // 0184 << Trio(oklabCyan); 0185 0186 constexpr double oklabMagenta[] = // 0187 {0.7016738534591195, 0.2745663787537365, -0.16915605971312353}; 0188 QTest::newRow("magenta 60.16894098715946 93.53959546199253 -60.50080231921204") // 0189 << cmsCIELab({60.16894098715946, 93.53959546199253, -60.50080231921204}) // 0190 << Trio(oklabMagenta); 0191 0192 constexpr double oklabYellow[] = // 0193 {0.9679827459780366, -0.0713691921107204, 0.1985698110545745}; 0194 QTest::newRow("yellow 97.60701009682253 -15.749846639252663 93.39361164266089") // 0195 << cmsCIELab({97.60701009682253, -15.749846639252663, 93.39361164266089}) // 0196 << Trio(oklabYellow); 0197 0198 constexpr double oklabBlack[] = // 0199 {0., 0., 0.}; 0200 QTest::newRow("black 0. 0. 0.") // 0201 << cmsCIELab({0., 0., 0.}) // 0202 << Trio(oklabBlack); 0203 0204 constexpr double oklabGray[] = // 0205 {0.599870811495933, -2.0178402559967168e-8, 4.1007266304848855e-8}; 0206 QTest::newRow("gray 53.5850142898864 -0.0000046837680400813 0.00000405887623511347") // 0207 << cmsCIELab({53.5850142898864, -0.0000046837680400813, 0.00000405887623511347}) // 0208 << Trio(oklabGray); 0209 } 0210 0211 private Q_SLOTS: 0212 void initTestCase() 0213 { 0214 // Called before the first test function is executed 0215 } 0216 0217 void cleanupTestCase() 0218 { 0219 // Called after the last test function was executed 0220 } 0221 0222 void init() 0223 { 0224 // Called before each test function is executed 0225 } 0226 0227 void cleanup() 0228 { 0229 // Called after every test function 0230 } 0231 0232 void testLch() 0233 { 0234 const auto myLch = GenericColor(51, 21, 1); 0235 GenericColor myLchResult = // 0236 AbsoluteColor::allConversions(ColorModel::CielabD50, myLch) // 0237 .value(ColorModel::CielabD50); 0238 QCOMPARE(myLchResult.first, 51); 0239 QCOMPARE(myLchResult.second, 21); 0240 QCOMPARE(myLchResult.third, 1); 0241 myLchResult = AbsoluteColor::convert(ColorModel::CielabD50, // 0242 myLch, // 0243 ColorModel::CielabD50) // 0244 .value(); 0245 QCOMPARE(myLchResult.first, 51); 0246 QCOMPARE(myLchResult.second, 21); 0247 QCOMPARE(myLchResult.third, 1); 0248 } 0249 0250 void testFromXyzd65ToOklab_data() 0251 { 0252 generateDataXyzd65Oklab(); 0253 } 0254 0255 void testFromXyzd65ToOklab() 0256 { 0257 QFETCH(double, x); 0258 QFETCH(double, y); 0259 QFETCH(double, z); 0260 const GenericColor xyz{x, y, z}; 0261 QFETCH(Trio, oklab); 0262 const auto actualResult = AbsoluteColor::fromXyzD65ToOklab(xyz); 0263 constexpr double epsilon = 0.001; 0264 QVERIFY(isNearlyEqual(actualResult.first, oklab(0, 0), epsilon)); 0265 QVERIFY(isNearlyEqual(actualResult.second, oklab(1, 0), epsilon)); 0266 QVERIFY(isNearlyEqual(actualResult.third, oklab(2, 0), epsilon)); 0267 } 0268 0269 void testFromOklabToXyzd65_data() 0270 { 0271 generateDataXyzd65Oklab(); 0272 } 0273 0274 void testFromOklabToXyzd65() 0275 { 0276 QFETCH(double, x); 0277 QFETCH(double, y); 0278 QFETCH(double, z); 0279 QFETCH(Trio, oklab); 0280 const auto actualXyzD65 = AbsoluteColor::fromOklabToXyzD65(GenericColor(oklab)); 0281 constexpr double epsilon = 0.001; 0282 QVERIFY(isNearlyEqual(actualXyzD65.first, x, epsilon)); 0283 QVERIFY(isNearlyEqual(actualXyzD65.second, y, epsilon)); 0284 QVERIFY(isNearlyEqual(actualXyzD65.third, z, epsilon)); 0285 } 0286 0287 void testFromCmscielabD50ToOklab_data() 0288 { 0289 generateDataCielabd50Oklab(); 0290 } 0291 0292 void testFromCmscielabD50ToOklab() 0293 { 0294 QFETCH(cmsCIELab, cmscielab); 0295 QFETCH(Trio, oklab); 0296 const auto actualResult = AbsoluteColor::convert( // 0297 ColorModel::CielabD50, // 0298 GenericColor(cmscielab), // 0299 ColorModel::OklabD65) // 0300 .value(); 0301 constexpr double epsilon = 0.001; 0302 QVERIFY(isNearlyEqual(actualResult.first, oklab(0, 0), epsilon)); 0303 QVERIFY(isNearlyEqual(actualResult.second, oklab(1, 0), epsilon)); 0304 QVERIFY(isNearlyEqual(actualResult.third, oklab(2, 0), epsilon)); 0305 } 0306 0307 void testFromOklabToCmscielabD50_data() 0308 { 0309 generateDataCielabd50Oklab(); 0310 } 0311 0312 void testFromOklabToCmscielabD50() 0313 { 0314 QFETCH(cmsCIELab, cmscielab); 0315 QFETCH(Trio, oklab); 0316 const auto actualCielabD50 = AbsoluteColor::convert( // 0317 ColorModel::OklabD65, // 0318 GenericColor(GenericColor(oklab)), // 0319 ColorModel::CielabD50) // 0320 .value(); 0321 // NOTE The original test data has been calculated converting from 0322 // CIELab-D50 to Oklab (which is always D65). Because of the different 0323 // whitepoint, a round-trip conversion of pure white is not possible. 0324 // As we use the same data to check the inverse conversion, we have 0325 // to choose a higher epsilon: 0326 constexpr double epsilon = 0.05; 0327 QVERIFY(isNearlyEqual(actualCielabD50.first, cmscielab.L, epsilon)); 0328 QVERIFY(isNearlyEqual(actualCielabD50.second, cmscielab.a, epsilon)); 0329 QVERIFY(isNearlyEqual(actualCielabD50.third, cmscielab.b, epsilon)); 0330 } 0331 }; 0332 0333 } // namespace PerceptualColor 0334 0335 QTEST_MAIN(PerceptualColor::TestAbsoluteColor) 0336 0337 // The following “include” is necessary because we do not use a header file: 0338 #include "testabsolutecolor.moc"