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)