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

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "TestLcmsRGBP2020PQColorSpace.h"
0008 
0009 #include <simpletest.h>
0010 #include <testpigment.h>
0011 
0012 #include "kis_debug.h"
0013 
0014 #include "KoColorProfile.h"
0015 #include "KoColorSpaceRegistry.h"
0016 #include "KoColor.h"
0017 #include "KoColorModelStandardIds.h"
0018 
0019 inline QString truncated(QString value) {
0020     value.truncate(24);
0021     return value;
0022 }
0023 
0024 enum SourceType {
0025     SDR,
0026     HDR,
0027     HDR_PQ
0028 };
0029 
0030 void testRoundTrip(const KoColorSpace *srcCS, const KoColorSpace *dstCS, SourceType sourceIsPQ)
0031 {
0032     /*
0033      *  On some systems these colorspaces cannot be created, so don't die:
0034      */
0035     if (!srcCS | !dstCS) return;
0036 
0037     KoColor srcColor(srcCS);
0038     KoColor dstColor(dstCS);
0039 
0040     QVector<float> refChannels;
0041 
0042     if (sourceIsPQ == HDR) {
0043         refChannels << 2.8; // R
0044         refChannels << 1.8; // G
0045         refChannels << 0.8; // B
0046         refChannels << 0.9; // A
0047     } else if (sourceIsPQ == HDR_PQ) {
0048         refChannels << 0.9; // R (PQ)
0049         refChannels << 0.7; // G (PQ)
0050         refChannels << 0.1; // B (PQ)
0051         refChannels << 0.9; // A
0052     } else if (sourceIsPQ == SDR) {
0053         refChannels << 0.15; // R
0054         refChannels << 0.17; // G
0055         refChannels << 0.19; // B
0056         refChannels << 0.90; // A
0057     }
0058 
0059     srcCS->fromNormalisedChannelsValue(srcColor.data(), refChannels);
0060 
0061     srcCS->convertPixelsTo(srcColor.data(), dstColor.data(), dstCS, 1,
0062                            KoColorConversionTransformation::internalRenderingIntent(),
0063                            KoColorConversionTransformation::internalConversionFlags());
0064 
0065     dstCS->convertPixelsTo(dstColor.data(), srcColor.data(), srcCS, 1,
0066                            KoColorConversionTransformation::internalRenderingIntent(),
0067                            KoColorConversionTransformation::internalConversionFlags());
0068 
0069     QVector<float> result(4);
0070     srcCS->normalisedChannelsValue(srcColor.data(), result);
0071 
0072     const QList<KoChannelInfo*> channels = srcCS->channels();
0073 
0074     // 5% tolerance for CMYK, 4% for 8-bit, and 1% for everything else
0075     const float tolerance =
0076         dstCS->colorModelId() == CMYKAColorModelID ? 0.05 :
0077         (dstCS->colorDepthId() == Integer8BitsColorDepthID ||
0078          srcCS->colorDepthId() == Integer8BitsColorDepthID) ? 0.04 :
0079         0.01;
0080 
0081     bool roundTripIsCorrect = true;
0082     for (int i = 0; i < 4; i++) {
0083         roundTripIsCorrect &= qAbs(refChannels[i] - result[i]) < tolerance;
0084     }
0085 
0086     if (!roundTripIsCorrect) {
0087         for (int i = 0; i < 4; i++) {
0088             qDebug() << channels[i]->name() << "ref" << refChannels[i] << "result" << result[i];
0089         }
0090     }
0091 
0092     QVERIFY(roundTripIsCorrect);
0093 }
0094 
0095 void testRoundTrip(const KoID &linearColorDepth, const KoID &pqColorDepth, SourceType sourceIsPQ)
0096 {
0097     const KoColorProfile *p2020PQProfile = KoColorSpaceRegistry::instance()->p2020PQProfile();
0098     const KoColorProfile *p2020G10Profile = KoColorSpaceRegistry::instance()->p2020G10Profile();
0099 
0100     const KoColorSpace *srcCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), linearColorDepth.id(), p2020G10Profile);
0101     const KoColorSpace *dstCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), pqColorDepth.id(), p2020PQProfile);;
0102 
0103     if (sourceIsPQ == HDR_PQ) {
0104         std::swap(srcCS, dstCS);
0105     }
0106 
0107     testRoundTrip(srcCS, dstCS, sourceIsPQ);
0108 }
0109 
0110 void TestLcmsRGBP2020PQColorSpace::test()
0111 {
0112     const KoColorProfile *p2020PQProfile = KoColorSpaceRegistry::instance()->p2020PQProfile();
0113     const KoColorProfile *p2020G10Profile = KoColorSpaceRegistry::instance()->p2020G10Profile();
0114     const KoColorProfile *p709G10Profile = KoColorSpaceRegistry::instance()->p709G10Profile();
0115 
0116     QVERIFY(p2020PQProfile);
0117     QVERIFY(p2020G10Profile);
0118     QVERIFY(p709G10Profile);
0119 
0120     QVector<KoID> linearModes;
0121     linearModes << Float16BitsColorDepthID;
0122     linearModes << Float32BitsColorDepthID;
0123 
0124     QVector<KoID> pqModes;
0125     pqModes << Integer8BitsColorDepthID;
0126     pqModes << Integer16BitsColorDepthID;
0127     pqModes << Float16BitsColorDepthID;
0128     pqModes << Float32BitsColorDepthID;
0129 
0130     Q_FOREACH(const KoID &src, linearModes) {
0131         Q_FOREACH(const KoID &dst, pqModes) {
0132             testRoundTrip(src, dst, HDR);
0133         }
0134     }
0135 
0136     Q_FOREACH(const KoID &src, linearModes) {
0137         Q_FOREACH(const KoID &dst, pqModes) {
0138             testRoundTrip(src, dst, HDR_PQ);
0139         }
0140     }
0141 }
0142 
0143 void TestLcmsRGBP2020PQColorSpace::testInternalConversions()
0144 {
0145     const KoColorProfile *p2020PQProfile = KoColorSpaceRegistry::instance()->p2020PQProfile();
0146 
0147     QVector<KoID> pqModes;
0148     pqModes << Integer16BitsColorDepthID;
0149     pqModes << Float16BitsColorDepthID;
0150     pqModes << Float32BitsColorDepthID;
0151 
0152     Q_FOREACH(const KoID &src, pqModes) {
0153         Q_FOREACH(const KoID &dst, pqModes) {
0154             if (src == dst) continue;
0155 
0156             const KoColorSpace *srcCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), src.id(), p2020PQProfile);
0157             const KoColorSpace *dstCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), dst.id(), p2020PQProfile);
0158 
0159             testRoundTrip(srcCS, dstCS, HDR_PQ);
0160         }
0161     }
0162 }
0163 
0164 void TestLcmsRGBP2020PQColorSpace::testConvertToCmyk()
0165 {
0166     const KoColorProfile *p2020PQProfile = KoColorSpaceRegistry::instance()->p2020PQProfile();
0167 
0168     const KoColorSpace *srcCS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), p2020PQProfile);
0169     const KoColorSpace *dstCS = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), 0);
0170 
0171     testRoundTrip(srcCS, dstCS, SDR);
0172 }
0173 
0174 void TestLcmsRGBP2020PQColorSpace::testHDRClippingRec709ToRec2020()
0175 {
0176     const KoColorProfile *p2020G10Profile = KoColorSpaceRegistry::instance()->p2020G10Profile();
0177     const KoColorProfile *p709G10Profile = KoColorSpaceRegistry::instance()->p709G10Profile();
0178 
0179     QVERIFY(p2020G10Profile);
0180     QVERIFY(p709G10Profile);
0181 
0182     const KoColorSpace *p709CS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), p709G10Profile);
0183     const KoColorSpace *p2020CS = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), p2020G10Profile);
0184 
0185     KoColor p709Color(Qt::white, p709CS);
0186 
0187     /**
0188      * Set the colors above the normal range to check if they are clipped during
0189      * the conversion
0190      */
0191     float* colorsPtr = reinterpret_cast<float*>(p709Color.data());
0192     colorsPtr[0] = 1.5f;
0193     colorsPtr[1] = 0.7f;
0194     colorsPtr[2] = 0.3f;
0195 
0196     KoColor p2020Color = p709Color.convertedTo(p2020CS);
0197     KoColor p709ConvertedColor = p2020Color.convertedTo(p709CS);
0198 
0199     QCOMPARE(p709Color, p709ConvertedColor);
0200 }
0201 
0202 KISTEST_MAIN(TestLcmsRGBP2020PQColorSpace)