File indexing completed on 2024-06-16 04:13:38

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "TestKoColor.h"
0008 
0009 #include <simpletest.h>
0010 
0011 #include <QDomElement>
0012 
0013 #include "KoColorModelStandardIds.h"
0014 
0015 #include "KoColor.h"
0016 #include "KoColorSpace.h"
0017 #include "KoColorProfile.h"
0018 #include "KoColorSpaceRegistry.h"
0019 #include "DebugPigment.h"
0020 #include "kis_debug.h"
0021 
0022 #include <testpigment.h>
0023 
0024 bool nearEqualValue(int a, int b)
0025 {
0026     return qAbs(a - b) <= 1;
0027 }
0028 
0029 void TestKoColor::testForModel(QString model)
0030 {
0031     QColor qc(200, 125, 100);
0032     QList<KoID> depthIDs = KoColorSpaceRegistry::instance()->colorDepthList(model, KoColorSpaceRegistry::AllColorSpaces);
0033     Q_FOREACH (const KoID& depthId, depthIDs) {
0034         const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(model, depthId.id() , "");
0035         if (cs) {
0036             KoColor kc(cs);
0037             kc.fromQColor(qc);
0038             QDomDocument doc;
0039             QDomElement elt = doc.createElement("color");
0040             kc.toXML(doc, elt);
0041             doc.appendChild(elt);
0042             dbgPigment << doc.toString();
0043             KoColor kcu = KoColor::fromXML(elt.firstChildElement(), depthId.id());
0044             QVERIFY2(*(kc.colorSpace()) == *(kcu.colorSpace()),
0045                      QString("Not identical color space (colorModelId = %1 depthId = %2) != (colorModelId = %3 depthId = %4) ")
0046                      .arg(kc.colorSpace()->colorModelId().id())
0047                      .arg(kc.colorSpace()->colorDepthId().id())
0048                      .arg(kcu.colorSpace()->colorModelId().id())
0049                      .arg(kcu.colorSpace()->colorDepthId().id()).toLatin1());
0050             QVERIFY(cs->difference(kcu.data(), kc.data()) <= 1);
0051         }
0052     }
0053 
0054 }
0055 
0056 void TestKoColor::testSerialization()
0057 {
0058     testForModel(RGBAColorModelID.id());
0059     testForModel(XYZAColorModelID.id());
0060     testForModel(LABAColorModelID.id());
0061     testForModel(CMYKAColorModelID.id());
0062     testForModel(GrayAColorModelID.id());
0063     // we cannot test ycbcr since we cannot ship profiles
0064     //testForModel(YCbCrAColorModelID.id());
0065 }
0066 
0067 void TestKoColor::testExistingSerializations()
0068 {
0069 
0070     QString main;
0071     QDomDocument doc;
0072 
0073     QColor c;
0074     // Test sRGB.
0075     main = "<sRGB r='0' g='0' b='1' />";
0076     doc.setContent(main);
0077     KoColor sRGB = KoColor::fromXML(doc.documentElement(), Integer8BitsColorDepthID.id());
0078     sRGB.toQColor(&c);
0079     QString blue = "#0000FF";
0080     QVERIFY2(c == QColor(blue), QString("XML parser is not loading the sRGB properly: \nresult: %1 \nexpected: %2")
0081              .arg(c.name()).arg(blue).toLatin1());
0082 
0083     // Test wide gamut RGB -- We can only check that the values deserialize properly, which is fine in this case.
0084     QString Rec2020profile = KoColorSpaceRegistry::instance()->p2020G10Profile()->name();
0085     main = QString("<RGB r='3.0' g='0' b='1' space='%1'/>").arg(Rec2020profile);
0086     doc.setContent(main);
0087     KoColor rec2020color = KoColor::fromXML(doc.documentElement(), Float32BitsColorDepthID.id());
0088 
0089     QVector<float> rec2020ChannelValues(4);
0090     rec2020color.colorSpace()->normalisedChannelsValue(rec2020color.data(), rec2020ChannelValues);
0091     QVERIFY(qFuzzyCompare(rec2020ChannelValues[0], 3.0f));
0092     QVERIFY(qFuzzyCompare(rec2020ChannelValues[1], 0.0f));
0093     QVERIFY(qFuzzyCompare(rec2020ChannelValues[2], 1.0f));
0094     QVERIFY(qFuzzyCompare(rec2020ChannelValues[3], 1.0f));
0095 
0096 
0097     // Test cmyk, we can only check that the channels deserialize properly here.
0098     // NOTE: 32bit float gives wildly different values here, so I am unsure what is going on still...
0099     const KoColorSpace *cmykSpace = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id());
0100     main = QString("<CMYK c='0.2' m='0.5' y='1.0' k='0.0' space='%1'/>").arg(cmykSpace->profile()->name());
0101     doc.setContent(main);
0102     KoColor cmykColorU8 = KoColor::fromXML(doc.documentElement(), Integer8BitsColorDepthID.id());
0103     KoColor cmykColorU16 = KoColor::fromXML(doc.documentElement(), Integer16BitsColorDepthID.id());
0104     KoColor cmykColorF32 = KoColor::fromXML(doc.documentElement(), Float32BitsColorDepthID.id());
0105 
0106     QVector<QDomElement> elements;
0107 
0108     elements.append(doc.documentElement());
0109 
0110     doc.setContent(cmykColorU8.toXML());
0111     elements.append(doc.documentElement().firstChild().toElement());
0112     doc.setContent(cmykColorU16.toXML());
0113     elements.append(doc.documentElement().firstChild().toElement());
0114     doc.setContent(cmykColorF32.toXML());
0115     elements.append(doc.documentElement().firstChild().toElement());
0116 
0117     QStringList attributes;
0118     attributes << "c" << "m" << "y" << "k";
0119 
0120     for (QString attr : attributes) {
0121         double mainValue = elements.first().attribute(attr).toDouble();
0122         for (QDomElement el: elements) {
0123             double compare = el.attribute(attr).toDouble();
0124             QVERIFY2(fabs(mainValue - compare) < 0.01
0125                      , QString("XML CMYK parsing has too high of a difference when roundtripping channel %1: %2")
0126                      .arg(attr).arg(mainValue - compare).toLatin1());
0127         }
0128     }
0129 
0130     // CMYK has wildly different values in F32 than in U8. Avoid F32 CMYK!
0131     //cmykColorU8.convertTo(cmykColorF32.colorSpace());
0132     //qDebug() << ppVar(cmykColorU8);
0133     //qDebug() << cmykColorU8.toXML();
0134     //qDebug() << cmykColorF32.colorSpace()->difference(cmykColorF32.data(), cmykColorU8.data());
0135 
0136     // Test XYZ - check channels.
0137     const KoColorSpace *xyzSpace = KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Integer8BitsColorDepthID.id());
0138     main = QString("<XYZ x='0.0' y='0.0' z='1.0' space='%1'/>").arg(xyzSpace->profile()->name());
0139     doc.setContent(main);
0140     KoColor xyzColor = KoColor::fromXML(doc.documentElement(), Integer8BitsColorDepthID.id());
0141     quint8 *xyzData = xyzColor.data();
0142     QCOMPARE(xyzData[0], 0);
0143     QCOMPARE(xyzData[1], 0);
0144     QCOMPARE(xyzData[2], 255);
0145 
0146 
0147     // Test LAB
0148     // Lab has a different way of handling floating point from the rest of the colorspaces.
0149     const KoColorSpace *labSpace = KoColorSpaceRegistry::instance()->lab16();
0150     main = QString("<Lab space='%1' L='34.67' a='54.1289' b='-90.3359' />").arg(labSpace->profile()->name());
0151     doc.setContent(main);
0152     KoColor LABcolorU8 = KoColor::fromXML(doc.documentElement(), Integer8BitsColorDepthID.id());
0153     KoColor LABcolorU16 = KoColor::fromXML(doc.documentElement(), Integer16BitsColorDepthID.id());
0154     KoColor LABcolorF32 = KoColor::fromXML(doc.documentElement(), Float32BitsColorDepthID.id());
0155 
0156     // Check that there isn't too much of a discrepancy between the XML values of the different bitdepths.
0157 
0158     elements.clear();
0159     elements.append(doc.documentElement());
0160 
0161     doc.setContent(LABcolorU8.toXML());
0162     elements.append(doc.documentElement().firstChild().toElement());
0163     doc.setContent(LABcolorU16.toXML());
0164     elements.append(doc.documentElement().firstChild().toElement());
0165     doc.setContent(LABcolorF32.toXML());
0166     elements.append(doc.documentElement().firstChild().toElement());
0167 
0168     attributes.clear();
0169     attributes << "L" << "a" << "b";
0170 
0171     for (QString attr : attributes) {
0172         double mainValue = elements.first().attribute(attr).toDouble();
0173         for (QDomElement el: elements) {
0174             double compare = el.attribute(attr).toDouble();
0175             QVERIFY2(fabs(mainValue - compare) < 1.0
0176                     , QString("XML LAB parsing has too high of a difference when roundtripping channel %1: %2")
0177                      .arg(attr).arg(mainValue - compare).toLatin1());
0178         }
0179     }
0180 
0181 
0182     // The following is the known sRGB color that the test value matches with.
0183     // Let's make sure that all the lab values roughly convert to this sRGB value.
0184     KoColor purpleCompare = KoColor(QColor("#442de9"), sRGB.colorSpace());
0185 
0186     LABcolorU8.convertTo(sRGB.colorSpace());
0187     QVERIFY2(sRGB.colorSpace()->difference(LABcolorU8.data(), purpleCompare.data()) <= 1
0188              , QString("LAB U8 has too high a difference to it's sRGB reference: %1")
0189              .arg(sRGB.colorSpace()->difference(LABcolorU8.data(), purpleCompare.data())).toLatin1());
0190     LABcolorU16.convertTo(sRGB.colorSpace());
0191     QVERIFY2(sRGB.colorSpace()->difference(LABcolorU16.data(), purpleCompare.data()) <= 1
0192              , QString("LAB U16 has too high a difference to it's sRGB reference: %1")
0193              .arg(sRGB.colorSpace()->difference(LABcolorU16.data(), purpleCompare.data())).toLatin1());
0194     LABcolorF32.convertTo(sRGB.colorSpace());
0195     QVERIFY2(sRGB.colorSpace()->difference(LABcolorF32.data(), purpleCompare.data()) <= 1
0196             , QString("LAB F32 has too high a difference to it's sRGB reference: %1")
0197             .arg(sRGB.colorSpace()->difference(LABcolorF32.data(), purpleCompare.data())).toLatin1());
0198 
0199     // Test Gray - check channels.
0200     const KoColorSpace *graySpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id());
0201     main = QString("<Gray g='0.5' space='%1'/>").arg(graySpace->profile()->name());
0202     doc.setContent(main);
0203     KoColor grayColor = KoColor::fromXML(doc.documentElement(), Integer8BitsColorDepthID.id());
0204     quint8 *grayData = grayColor.data();
0205     QCOMPARE(grayData[0], 128);
0206 
0207 }
0208 
0209 void TestKoColor::testConversion()
0210 {
0211     QColor c = Qt::red;
0212     const KoColorSpace *csOrig = KoColorSpaceRegistry::instance()->rgb8();
0213     const KoColorSpace *csDst = KoColorSpaceRegistry::instance()->lab16();
0214 
0215     KoColor kc(csOrig);
0216     kc.fromQColor(c);
0217 
0218     kc.convertTo(csDst);
0219 }
0220 
0221 void TestKoColor::testSimpleSerialization()
0222 {
0223     QColor c = Qt::green;
0224     KoColor k;
0225     k.fromQColor(c);
0226     QString xml = k.toXML();
0227     KoColor k2 = KoColor::fromXML(xml);
0228     QVERIFY(k2.colorSpace() == k.colorSpace());
0229 }
0230 
0231 void TestKoColor::testComparison()
0232 {
0233     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
0234     KoColor c1(Qt::white, cs);
0235     KoColor c2(Qt::white, cs);
0236     KoColor c3(Qt::black, cs);
0237 
0238     QVERIFY(c1 == c2);
0239     QVERIFY(c2 != c3);
0240 }
0241 
0242 void TestKoColor::testComparisonQVariant()
0243 {
0244     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
0245     KoColor c1(Qt::white, cs);
0246     KoColor c2(Qt::white, cs);
0247     KoColor c3(Qt::black, cs);
0248 
0249     QVariant v1 = QVariant::fromValue(c1);
0250     QVariant v2 = QVariant::fromValue(c2);
0251     QVariant v3 = QVariant::fromValue(c3);
0252 
0253     QVERIFY(v1 == v2);
0254     QVERIFY(v2 != v3);
0255 }
0256 
0257 void TestKoColor::testSVGParsing()
0258 {
0259     QHash <QString, const KoColorProfile *> profileList;
0260 
0261     //1. Testing case with fallback hex value and nonsense icc-color that we cannot parse
0262 
0263     KoColor p1 = KoColor::fromSVG11("#ff0000 icc-color(blah, 0.0, 1.0, 1.0, 0.0);", profileList);
0264     const KoColorSpace *sRGB = KoColorSpaceRegistry::instance()->rgb16(KoColorSpaceRegistry::instance()->p709SRGBProfile());
0265     KoColor c1 = KoColor(QColor("#ff0000"), sRGB);
0266 
0267     QVERIFY2(p1 == c1
0268              , QString("SVG11 parser is not loading the sRGB hex fallback: \nresult: %1 \nexpected: %2")
0269              .arg(KoColor::toQString(p1)).arg(KoColor::toQString(c1)).toLatin1());
0270 
0271     //2. testing case with fallback colorname and nonsense icc-color that we cannot parse
0272 
0273     KoColor p2 = KoColor::fromSVG11("#ff0000 silver icc-color(blah, 0.0, 1.0, 1.0, 0.0);", profileList);
0274     KoColor c2 = KoColor(QColor("silver"), sRGB);
0275 
0276     QVERIFY2(p2 == c2
0277              , QString("SVG11 parser is not loading the sRGB colorname fallback: \nresult: %1 \nexpected: %2")
0278              .arg(KoColor::toQString(p2)).arg(KoColor::toQString(c2)).toLatin1());
0279 
0280     //3. testing case with fallback color and useful icc-color
0281 
0282     const KoColorSpace *cmyk = KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id());
0283     QString cmykName = "sillyCMYKName";
0284     profileList.insert(cmykName, cmyk->profile());
0285 
0286     KoColor p3 = KoColor::fromSVG11("#ff0000 silver icc-color("+cmykName+", 0.0, 0.0, 1.0, 1.0);", profileList);
0287     KoColor c3 = KoColor::fromXML("<color channeldepth='U16'><CMYK c='0.0' m='0.0' y='1.0' k='1.0' space='"+cmyk->profile()->name()+"'/></color>");
0288 
0289     QVERIFY2(p3 == c3
0290              , QString("SVG11 parsed cmyk incorrectly: \nresult: %1 \nexpected: %2")
0291              .arg(KoColor::toQString(p3)).arg(KoColor::toQString(c3)).toLatin1());
0292     //4. Roundtrip
0293 
0294     KoColor c4(KoColorSpaceRegistry::instance()->lab16());
0295     c4.fromQColor(QColor("#426471"));
0296     QString value = c4.toSVG11(&profileList);
0297     qDebug() << value;
0298     KoColor p4 = KoColor::fromSVG11(value, profileList);
0299 
0300     QVERIFY2(c4.colorSpace()->difference(p4.data(), c4.data()) < 1.0
0301              , QString("Difference between colors from serialization roundtrip above 1.0: %1")
0302              .arg(c4.colorSpace()->difference(p4.data(), c4.data())).toLatin1());
0303 
0304     //4.5 Check that the size stays the same even though we already added this profile to the stack before.
0305     int profileListSize = profileList.size();
0306     QString newColor = c4.toSVG11(&profileList);
0307     QCOMPARE(profileList.size(), profileListSize);
0308 
0309     //5. Testing rgb...
0310 
0311     KoColor p5 = KoColor::fromSVG11("#ff0000 rgb(100, 50, 50%)", profileList);
0312     KoColor c5 = KoColor(QColor(100, 50, 127), sRGB);
0313 
0314     QVERIFY2(p5 == c5, QString("the rgb() definition for SVG11 is not parsed correctly, \nresult: %1 \nexpected: %2")
0315              .arg(KoColor::toQString(p5)).arg(KoColor::toQString(c5)).toLatin1());
0316 
0317     //6. Testing special srgb definition... especially the part where it can be defined case-insensitive.
0318 
0319     KoColor p6 = KoColor::fromSVG11("#ff0000 icc-color(srgb, 1.0, 1.0, 0.0)", profileList);
0320     KoColor c6 = KoColor::fromXML("<color channeldepth='F32'><sRGB r='1.0' g='1.0' b='0.0'/></color>");
0321 
0322     QVERIFY2(p6 == c6
0323              , QString("sRGB parsing is different between SVG11 and XML: \nresult: %1 \nexpected: %2")
0324              .arg(KoColor::toQString(p6)).arg(KoColor::toQString(c6)).toLatin1());
0325     //7. Testing out-of-bounds values...
0326 
0327     KoColor p7 = KoColor::fromSVG11("#ff0000 icc-color(srgb, 2.0, 1.0, 0.0)", profileList);
0328     KoColor c7 = KoColor::fromXML("<color channeldepth='F32'><sRGB r='2.0' g='1.0' b='0.0'/></color>");
0329 
0330     QVERIFY2(p7 == c7, QString("Out of bounds RGB is parsing differently for XML and SVG11: \nresult: %1 \nexpected: %2")
0331              .arg(KoColor::toQString(p7)).arg(KoColor::toQString(c7)).toLatin1());
0332 
0333     //8. Check lab special case.
0334     KoColor p8 = KoColor::fromSVG11("#ff0000 icc-color(lab, 34.67, 54.1289, -90.3359)", profileList);
0335     QDomDocument doc;
0336     doc.setContent(QString("<Lab space='%1' L='34.67' a='54.1289' b='-90.3359' />").arg(c4.colorSpace()->profile()->name()));
0337     KoColor c8 = KoColor::fromXML(doc.documentElement(), "U16");
0338 
0339     QVERIFY2(p8 == c8, QString("Lab parsing is giving different values for XML and SVG11: \nresult: %1 \nexpected: %2")
0340                                .arg(KoColor::toQString(p8)).arg(KoColor::toQString(c8)).toLatin1());
0341     //9. Check xyz loading
0342     //We do not support XYZ because Inkscape decided that XYZ X and Z are 0-2, and I cannot figure out why.
0343     const KoColorSpace *xyzSpace = KoColorSpaceRegistry::instance()->colorSpace(XYZAColorModelID.id(), Integer16BitsColorDepthID.id());
0344     profileList.insert("XYZ", xyzSpace->profile());
0345     KoColor p9 = KoColor::fromSVG11("#0077FF icc-color(XYZ, 1.0, 0.0, 0.5)", profileList);
0346     KoColor c9 = KoColor(QColor("#0077FF"), sRGB);
0347     QVERIFY2(p9 == c9
0348              , QString("SVG11 parser is not loading the sRGB hex fallback for XYZ: \nresult: %1 \nexpected: %2")
0349              .arg(KoColor::toQString(p9)).arg(KoColor::toQString(c9)).toLatin1());
0350 
0351     //10. Check xyz saving
0352     KoColor p10 = KoColor(sRGB);
0353     QString c10 = "#0077FF";
0354     p10.fromQColor(QColor(c10));
0355     p10.convertTo(xyzSpace);
0356 
0357     QVERIFY2(p10.toSVG11(&profileList) != c10, QString("XYZ values are being saved: \nresult: %1 \nexpected: %2")
0358              .arg(KoColor::toQString(p10)).arg(c10).toLatin1());
0359 
0360     //11. Check gray loading.
0361     const KoColorSpace *gray = KoColorSpaceRegistry::instance()->graya8();
0362     profileList.insert("grayName", gray->profile());
0363     KoColor p11 = KoColor::fromSVG11("#ff0000 icc-color(grayName, 0.21);", profileList);
0364     KoColor c11 = KoColor::fromXML("<color channeldepth='F32'><Gray g='0.21' space='"+gray->profile()->name()+"'/></color>");
0365 
0366     QVERIFY2(p11 == c11
0367              , QString("SVG11 parsed gray incorrectly: \nresult: %1 \nexpected: %2")
0368              .arg(KoColor::toQString(p11)).arg(KoColor::toQString(c11)).toLatin1());
0369 
0370     //12. check sRGB is not saved.
0371     profileListSize = profileList.size();
0372     QString colorDef = c1.toSVG11(&profileList);
0373     QCOMPARE(profileList.size(), profileListSize);
0374 
0375 }
0376 
0377 KISTEST_MAIN(TestKoColor)