File indexing completed on 2024-06-16 10:43:42

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"