File indexing completed on 2024-10-20 04:17:41
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 "rgbcolor.h" 0007 0008 #include "genericcolor.h" 0009 #include <qcolor.h> 0010 #include <qglobal.h> 0011 #include <qnamespace.h> 0012 #include <qobject.h> 0013 #include <qscopedpointer.h> 0014 #include <qtest.h> 0015 #include <qtestcase.h> 0016 #include <utility> 0017 0018 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0019 #include <qtmetamacros.h> 0020 #else 0021 #include <qobjectdefs.h> 0022 #include <qstring.h> 0023 #endif 0024 0025 namespace PerceptualColor 0026 { 0027 class TestRgbColor : public QObject 0028 { 0029 Q_OBJECT 0030 0031 public: 0032 explicit TestRgbColor(QObject *parent = nullptr) 0033 : QObject(parent) 0034 { 0035 } 0036 0037 private: 0038 template<typename T> 0039 bool isAlmostEqual(T first, T second, T tolerance = 3) 0040 { 0041 return qAbs<T>(first - second) < tolerance; 0042 } 0043 0044 private Q_SLOTS: 0045 void initTestCase() 0046 { 0047 // Called before the first test function is executed 0048 } 0049 0050 void cleanupTestCase() 0051 { 0052 // Called after the last test function was executed 0053 } 0054 0055 void init() 0056 { 0057 // Called before each test function is executed 0058 } 0059 0060 void cleanup() 0061 { 0062 // Called after every test function 0063 } 0064 0065 void testConstructorDestructorStatic() 0066 { 0067 RgbColor myColor; 0068 } 0069 0070 void testConstructorDestructorDynamic() 0071 { 0072 QScopedPointer<RgbColor> myPointer{new RgbColor()}; 0073 } 0074 0075 void testDefaultConstructor() 0076 { 0077 RgbColor myColor; 0078 QCOMPARE(myColor.rgbQColor.isValid(), false); 0079 } 0080 0081 void testCopyConstructorUninitialized() 0082 { 0083 RgbColor myColor1; 0084 RgbColor myColor2(myColor1); 0085 QCOMPARE(myColor2.hsl, myColor1.hsl); 0086 QCOMPARE(myColor2.hsv, myColor1.hsv); 0087 QCOMPARE(myColor2.hwb, myColor1.hwb); 0088 QCOMPARE(myColor2.rgb255, myColor1.rgb255); 0089 QCOMPARE(myColor2.rgbQColor, myColor1.rgbQColor); 0090 } 0091 0092 void testCopyConstructor() 0093 { 0094 RgbColor myColor1 = RgbColor::fromRgb255(GenericColor{1, 2, 3}); 0095 RgbColor myColor2(myColor1); 0096 QCOMPARE(myColor2.hsl, myColor1.hsl); 0097 QCOMPARE(myColor2.hsv, myColor1.hsv); 0098 QCOMPARE(myColor2.hwb, myColor1.hwb); 0099 QCOMPARE(myColor2.rgb255, myColor1.rgb255); 0100 QCOMPARE(myColor2.rgbQColor, myColor1.rgbQColor); 0101 } 0102 0103 void testCopyAssignmentUninitialized() 0104 { 0105 RgbColor myColor1; 0106 RgbColor myColor2; 0107 myColor2 = myColor1; 0108 QCOMPARE(myColor2.hsl, myColor1.hsl); 0109 QCOMPARE(myColor2.hsv, myColor1.hsv); 0110 QCOMPARE(myColor2.hwb, myColor1.hwb); 0111 QCOMPARE(myColor2.rgb255, myColor1.rgb255); 0112 QCOMPARE(myColor2.rgbQColor, myColor1.rgbQColor); 0113 } 0114 0115 void testCopyAssignment() 0116 { 0117 RgbColor myColor1 = RgbColor::fromRgb255(GenericColor{4, 5, 6}); 0118 RgbColor myColor2 = RgbColor::fromRgb255(GenericColor{7, 8, 9}); 0119 Q_UNUSED(myColor2); 0120 myColor2 = myColor1; 0121 QCOMPARE(myColor2.hsl, myColor1.hsl); 0122 QCOMPARE(myColor2.hsv, myColor1.hsv); 0123 QCOMPARE(myColor2.hwb, myColor1.hwb); 0124 QCOMPARE(myColor2.rgb255, myColor1.rgb255); 0125 QCOMPARE(myColor2.rgbQColor, myColor1.rgbQColor); 0126 } 0127 0128 void testMoveConstructorUninitialized() 0129 { 0130 RgbColor myReference; 0131 RgbColor myColor1 = myReference; 0132 RgbColor myColor2(std::move(myColor1)); 0133 QCOMPARE(myColor2.hsl, myColor1.hsl); 0134 QCOMPARE(myColor2.hsv, myColor1.hsv); 0135 QCOMPARE(myColor2.hwb, myColor1.hwb); 0136 QCOMPARE(myColor2.rgb255, myColor1.rgb255); 0137 QCOMPARE(myColor2.rgbQColor, myColor1.rgbQColor); 0138 } 0139 0140 void testMoveConstructor() 0141 { 0142 RgbColor myReference = RgbColor::fromRgbQColor(Qt::yellow); 0143 RgbColor myColor1 = myReference; 0144 RgbColor myColor2(std::move(myColor1)); 0145 QCOMPARE(myColor2.hsl, myColor1.hsl); 0146 QCOMPARE(myColor2.hsv, myColor1.hsv); 0147 QCOMPARE(myColor2.hwb, myColor1.hwb); 0148 QCOMPARE(myColor2.rgb255, myColor1.rgb255); 0149 QCOMPARE(myColor2.rgbQColor, myColor1.rgbQColor); 0150 } 0151 0152 void testMoveAssignmentUninitialized() 0153 { 0154 RgbColor myReference; 0155 RgbColor myColor1 = myReference; 0156 RgbColor myColor2; 0157 myColor2 = std::move(myColor1); 0158 QCOMPARE(myColor2.hsl, myColor1.hsl); 0159 QCOMPARE(myColor2.hsv, myColor1.hsv); 0160 QCOMPARE(myColor2.hwb, myColor1.hwb); 0161 QCOMPARE(myColor2.rgb255, myColor1.rgb255); 0162 QCOMPARE(myColor2.rgbQColor, myColor1.rgbQColor); 0163 } 0164 0165 void testMoveAssignment() 0166 { 0167 RgbColor myReference = RgbColor::fromRgbQColor(Qt::red); 0168 RgbColor myColor1 = myReference; 0169 RgbColor myColor2; 0170 myColor2 = std::move(myColor1); 0171 QCOMPARE(myColor2.hsl, myColor1.hsl); 0172 QCOMPARE(myColor2.hsv, myColor1.hsv); 0173 QCOMPARE(myColor2.hwb, myColor1.hwb); 0174 QCOMPARE(myColor2.rgb255, myColor1.rgb255); 0175 QCOMPARE(myColor2.rgbQColor, myColor1.rgbQColor); 0176 } 0177 0178 void testFromRgb() 0179 { 0180 RgbColor myColor1 = RgbColor::fromRgb255(GenericColor{113, 53, 23}); 0181 QCOMPARE(myColor1.rgbQColor, QColor::fromRgb(113, 53, 23)); 0182 } 0183 0184 void testFromRgbQColor() 0185 { 0186 RgbColor myColor1 = RgbColor::fromRgbQColor(Qt::yellow); 0187 QCOMPARE(myColor1.rgbQColor, Qt::yellow); 0188 } 0189 0190 void testRgbHue() 0191 { 0192 // The hue of the RGB-based HSV, HSL and HBW is identical. 0193 RgbColor value; 0194 0195 value = RgbColor::fromHsl(GenericColor{150, 40, 30}); 0196 QCOMPARE(value.hsl.first, 150); 0197 QCOMPARE(value.hsv.first, 150); 0198 0199 value = RgbColor::fromHsv(GenericColor{150, 40, 30}); 0200 QCOMPARE(value.hsl.first, 150); 0201 QCOMPARE(value.hsv.first, 150); 0202 } 0203 0204 void testRgbHueOnGrayAxis() 0205 { 0206 // The hue of the RGB-based HSV, HSL and HBW is identical, 0207 // even when the value is on the gray axis. 0208 RgbColor value; 0209 0210 value = RgbColor::fromHsl(GenericColor{150, 0, 50}); 0211 QCOMPARE(value.hsl.first, 150); 0212 QCOMPARE(value.hsv.first, 150); 0213 QCOMPARE(value.hwb.first, 150); 0214 0215 value = RgbColor::fromHsv(GenericColor{150, 0, 50}); 0216 QCOMPARE(value.hsl.first, 150); 0217 QCOMPARE(value.hsv.first, 150); 0218 QCOMPARE(value.hwb.first, 150); 0219 0220 value = RgbColor::fromHwb(GenericColor{150, 50, 50}); 0221 // Sum of w and b is 100. 0222 QCOMPARE(value.hsl.first, 150); 0223 QCOMPARE(value.hsv.first, 150); 0224 QCOMPARE(value.hwb.first, 150); 0225 0226 value = RgbColor::fromHwb(GenericColor{150, 70, 70}); 0227 // Sum of w and b is more than 100. 0228 QCOMPARE(value.hsl.first, 150); 0229 QCOMPARE(value.hsv.first, 150); 0230 QCOMPARE(value.hwb.first, 150); 0231 0232 value = RgbColor::fromRgb255(GenericColor{120, 120, 120}); 0233 // An RGB value on the gray axis does not provide any information 0234 // about the hue. We can reasonably expect a standard value: 0°. 0235 QCOMPARE(value.hsl.first, 0); 0236 QCOMPARE(value.hsv.first, 0); 0237 QCOMPARE(value.hwb.first, 0); 0238 } 0239 0240 void testHueFromRgbToLchSaturationContinuityWhite() 0241 { 0242 // LCH-hue values can be arbitrary when the color is on the gray axis. 0243 // For usability reasons, we should have nevertheless meaningful 0244 // hue values. 0245 } 0246 0247 void testFromHsl() 0248 { 0249 const RgbColor value = RgbColor::fromHsl(GenericColor{100, 60, 30}); 0250 0251 QVERIFY(isAlmostEqual<double>(value.hsl.first, 100)); 0252 QVERIFY(isAlmostEqual<double>(value.hsl.second, 60)); 0253 QVERIFY(isAlmostEqual<double>(value.hsl.third, 30)); 0254 0255 QVERIFY(isAlmostEqual<double>(value.hsv.first, 100)); 0256 QVERIFY(isAlmostEqual<double>(value.hsv.second, 75)); 0257 QVERIFY(isAlmostEqual<double>(value.hsv.third, 48)); 0258 0259 QVERIFY(isAlmostEqual<double>(value.hwb.first, 100)); 0260 QVERIFY(isAlmostEqual<double>(value.hwb.second, 12)); 0261 QVERIFY(isAlmostEqual<double>(value.hwb.third, 52)); 0262 0263 QVERIFY(isAlmostEqual<double>(value.rgb255.first, 61)); 0264 QVERIFY(isAlmostEqual<double>(value.rgb255.second, 122)); 0265 QVERIFY(isAlmostEqual<double>(value.rgb255.third, 31)); 0266 } 0267 0268 void testFromHsv() 0269 { 0270 const RgbColor value = RgbColor::fromHsv(GenericColor{100, 60, 30}); 0271 0272 QVERIFY(isAlmostEqual<double>(value.hsl.first, 100)); 0273 QVERIFY(isAlmostEqual<double>(value.hsl.second, 43)); 0274 QVERIFY(isAlmostEqual<double>(value.hsl.third, 21)); 0275 0276 QVERIFY(isAlmostEqual<double>(value.hsv.first, 100)); 0277 QVERIFY(isAlmostEqual<double>(value.hsv.second, 60)); 0278 QVERIFY(isAlmostEqual<double>(value.hsv.third, 30)); 0279 0280 QVERIFY(isAlmostEqual<double>(value.hwb.first, 100)); 0281 QVERIFY(isAlmostEqual<double>(value.hwb.second, 12)); 0282 QVERIFY(isAlmostEqual<double>(value.hwb.third, 70)); 0283 0284 QVERIFY(isAlmostEqual<double>(value.rgb255.first, 45)); 0285 QVERIFY(isAlmostEqual<double>(value.rgb255.second, 76)); 0286 QVERIFY(isAlmostEqual<double>(value.rgb255.third, 30)); 0287 } 0288 0289 void testSaturationSynchronizationForBlackFromHsv() 0290 { 0291 // HSV-saturation and HSL-saturation are different. However, when 0292 // the color is black, changing any of these two saturation types 0293 // does not modify the color. But near to the blackpoint, 0294 // HSV-saturation and HSL-saturation behave very similar, 0295 // while they become more different the larger we get away 0296 // from the blackpoint. Therefore, it seems somewhat logical 0297 // that both are synchronized if (and only if) the color is black. 0298 0299 RgbColor value; 0300 0301 value = RgbColor::fromHsv(GenericColor{150, 100, 0}); 0302 QVERIFY(isAlmostEqual<double>(value.hsl.second, 100)); 0303 0304 value = RgbColor::fromHsv(GenericColor{150, 60, 0}); 0305 QVERIFY(isAlmostEqual<double>(value.hsl.second, 60)); 0306 0307 value = RgbColor::fromHsv(GenericColor{150, 30, 0}); 0308 QVERIFY(isAlmostEqual<double>(value.hsl.second, 30)); 0309 0310 value = RgbColor::fromHsv(GenericColor{150, 0, 0}); 0311 QVERIFY(isAlmostEqual<double>(value.hsl.second, 0)); 0312 } 0313 0314 void testSaturationSynchronizationForBlackFromHsl() 0315 { 0316 // HSV-saturation and HSL-saturation are different. However, when 0317 // the color is black, changing any of these two saturation types 0318 // does not modify the color. But near to the blackpoint, 0319 // HSV-saturation and HSL-saturation behave very similar, 0320 // while they become more different the larger we get away 0321 // from the blackpoint. Therefore, it seems somewhat logical 0322 // that both are synchronized if (and only if) the color is black. 0323 0324 RgbColor value; 0325 0326 value = RgbColor::fromHsl(GenericColor{150, 100, 0}); 0327 QVERIFY(isAlmostEqual<double>(value.hsv.second, 100)); 0328 0329 value = RgbColor::fromHsl(GenericColor{150, 60, 0}); 0330 QVERIFY(isAlmostEqual<double>(value.hsv.second, 60)); 0331 0332 value = RgbColor::fromHsl(GenericColor{150, 30, 0}); 0333 QVERIFY(isAlmostEqual<double>(value.hsv.second, 30)); 0334 0335 value = RgbColor::fromHsl(GenericColor{150, 0, 0}); 0336 QVERIFY(isAlmostEqual<double>(value.hsv.second, 0)); 0337 } 0338 0339 void testSaturationSynchronizationForBlackFromOther() 0340 { 0341 // HSV-saturation and HSL-saturation are different. However, when 0342 // the color is black, changing any of these two saturation types 0343 // does not modify the color. When converting from a color format 0344 // different from HSV and HSL, there is no information about the 0345 // saturation, so the saturation could be anything within the valid 0346 // range. But for usability, it is better that for all conversions 0347 // of black, we get always the same saturation value. Given that 0348 // for white, we want always 0% for different reasons, and that all 0349 // the gray axis between black and white has also a saturation of 0% 0350 // for both saturation types, it seems natural to use also 0% for 0351 // black, so that the whole gray axis has a uniform saturation 0352 // value. 0353 0354 RgbColor value; 0355 0356 constexpr int saturationOfBlackColor = 0; 0357 0358 value = RgbColor::fromRgb255(GenericColor{0, 0, 0}); 0359 QVERIFY(isAlmostEqual<double>(value.hsv.second, saturationOfBlackColor)); 0360 QVERIFY(isAlmostEqual<double>(value.hsl.second, saturationOfBlackColor)); 0361 0362 value = RgbColor::fromHwb(GenericColor{320, 0, 100}); 0363 QVERIFY(isAlmostEqual<double>(value.hsv.second, saturationOfBlackColor)); 0364 QVERIFY(isAlmostEqual<double>(value.hsl.second, saturationOfBlackColor)); 0365 } 0366 0367 void testHslSaturationForWhite() 0368 { 0369 // For white, the HSV-saturation is necessarily 0%, while the 0370 // HSL-saturation might have any valid values (0%–100%). It is better 0371 // for usability to get in any situation always the same value for 0372 // HSL-saturation for white color. It seems natural to choose 0% as 0373 // a standard value, because this is synchronized with HSV-saturation, 0374 // and especially because the whole gray axis between black and white 0375 // has 0% as saturation anyway, so it is nice to have a uniform 0376 // saturation value for the whole gray axis (including black and 0377 // white). 0378 0379 RgbColor value; 0380 0381 constexpr int saturationOfWhiteColor = 0; 0382 0383 value = RgbColor::fromHsl(GenericColor{320, 50, 100}); 0384 // Expect a non-standard value because original values 0385 // should never be changed. 0386 QVERIFY(isAlmostEqual<double>(value.hsl.second, 50)); 0387 0388 // All other original color formats should give the standard 0389 // HSL-saturation for white: 0390 0391 value = RgbColor::fromRgb255(GenericColor{255, 255, 255}); 0392 // Expect a non-standard value because original values 0393 // should never be changed. 0394 QVERIFY(isAlmostEqual<double>(value.hsl.second, saturationOfWhiteColor)); 0395 0396 value = RgbColor::fromHsv(GenericColor{320, 0, 100}); 0397 // Expect a non-standard value because original values 0398 // should never be changed. 0399 QVERIFY(isAlmostEqual<double>(value.hsl.second, saturationOfWhiteColor)); 0400 0401 value = RgbColor::fromHwb(GenericColor{320, 100, 0}); 0402 // Expect a non-standard value because original values 0403 // should never be changed. 0404 QVERIFY(isAlmostEqual<double>(value.hsl.second, saturationOfWhiteColor)); 0405 } 0406 0407 void testFromHwb() 0408 { 0409 const RgbColor value = RgbColor::fromHwb(GenericColor{100, 60, 30}); 0410 0411 QVERIFY(isAlmostEqual<double>(value.hsl.first, 100)); 0412 QVERIFY(isAlmostEqual<double>(value.hsl.second, 14)); 0413 QVERIFY(isAlmostEqual<double>(value.hsl.third, 65)); // 0414 0415 QVERIFY(isAlmostEqual<double>(value.hsv.first, 100)); 0416 QVERIFY(isAlmostEqual<double>(value.hsv.second, 15)); 0417 QVERIFY(isAlmostEqual<double>(value.hsv.third, 70)); // 0418 0419 QVERIFY(isAlmostEqual<double>(value.hwb.first, 100)); 0420 QVERIFY(isAlmostEqual<double>(value.hwb.second, 60)); 0421 QVERIFY(isAlmostEqual<double>(value.hwb.third, 30)); // 0422 0423 QVERIFY(isAlmostEqual<double>(value.rgb255.first, 162)); 0424 QVERIFY(isAlmostEqual<double>(value.rgb255.second, 179)); 0425 QVERIFY(isAlmostEqual<double>(value.rgb255.third, 153)); // 0426 } 0427 0428 void testFromHwbDenormalized() 0429 { 0430 const RgbColor value = RgbColor::fromHwb(GenericColor{100, 70, 70}); 0431 // The sum of w and b is greater than 100. This is denormalized. 0432 0433 QVERIFY(isAlmostEqual<double>(value.hsl.first, 100)); 0434 QVERIFY(isAlmostEqual<double>(value.hsl.second, 0)); 0435 QVERIFY(isAlmostEqual<double>(value.hsl.third, 50)); // 0436 0437 QVERIFY(isAlmostEqual<double>(value.hsv.first, 100)); 0438 QVERIFY(isAlmostEqual<double>(value.hsv.second, 0)); 0439 QVERIFY(isAlmostEqual<double>(value.hsv.third, 50)); // 0440 0441 QVERIFY(isAlmostEqual<double>(value.hwb.first, 100)); 0442 QVERIFY(isAlmostEqual<double>(value.hwb.second, 70)); 0443 QVERIFY(isAlmostEqual<double>(value.hwb.third, 70)); // 0444 0445 QVERIFY(isAlmostEqual<double>(value.rgb255.first, 128)); 0446 QVERIFY(isAlmostEqual<double>(value.rgb255.second, 128)); 0447 QVERIFY(isAlmostEqual<double>(value.rgb255.third, 128)); // 0448 } 0449 0450 void testEquality() 0451 { 0452 RgbColor myColor1 = RgbColor::fromRgb255(GenericColor{1, 2, 3}); 0453 RgbColor myColor2 = RgbColor::fromRgb255(GenericColor{1, 2, 3}); 0454 QVERIFY(myColor1 == myColor2); 0455 myColor2.rgb255.first += 1; 0456 QVERIFY(!(myColor1 == myColor2)); 0457 } 0458 }; 0459 0460 } // namespace PerceptualColor 0461 0462 QTEST_MAIN(PerceptualColor::TestRgbColor) 0463 0464 // The following “include” is necessary because we do not use a header file: 0465 #include "testrgbcolor.moc"