File indexing completed on 2024-12-01 04:21:11
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 "chromalightnessdiagram.h" 0007 // Second, the private implementation. 0008 #include "chromalightnessdiagram_p.h" // IWYU pragma: keep 0009 0010 #include "constpropagatinguniquepointer.h" 0011 #include "helper.h" 0012 #include "helpermath.h" 0013 #include "lchdouble.h" 0014 #include "rgbcolorspacefactory.h" 0015 #include <cmath> 0016 #include <limits> 0017 #include <qglobal.h> 0018 #include <qlist.h> 0019 #include <qnamespace.h> 0020 #include <qobject.h> 0021 #include <qpoint.h> 0022 #include <qrect.h> 0023 #include <qsharedpointer.h> 0024 #include <qsignalspy.h> 0025 #include <qsize.h> 0026 #include <qtest.h> 0027 #include <qtestcase.h> 0028 #include <qtestkeyboard.h> 0029 #include <qtestmouse.h> 0030 0031 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0032 #include <qtmetamacros.h> 0033 #else 0034 #include <qobjectdefs.h> 0035 #include <qstring.h> 0036 #endif 0037 0038 namespace PerceptualColor 0039 { 0040 class RgbColorSpace; 0041 0042 class TestChromaLightnessDiagram : public QObject 0043 { 0044 Q_OBJECT 0045 0046 public: 0047 explicit TestChromaLightnessDiagram(QObject *parent = nullptr) 0048 : QObject(parent) 0049 { 0050 } 0051 0052 private: 0053 QSharedPointer<PerceptualColor::RgbColorSpace> m_rgbColorSpace = RgbColorSpaceFactory::createSrgb(); 0054 0055 private Q_SLOTS: 0056 void initTestCase() 0057 { 0058 // Called before the first test function is executed 0059 } 0060 0061 void cleanupTestCase() 0062 { 0063 // Called after the last test function was executed 0064 } 0065 0066 void init() 0067 { 0068 // Called before each test function is executed 0069 } 0070 0071 void cleanup() 0072 { 0073 // Called after every test function 0074 } 0075 0076 void testConstructorDestructor() 0077 { 0078 ChromaLightnessDiagram test(m_rgbColorSpace); 0079 } 0080 0081 void testVerySmallWidgetSizes() 0082 { 0083 // Also very small widget sizes should not crash the widget. 0084 // This might happen because of divisions by 0, even when the widget 0085 // is bigger than 0 because of borders or offsets. We test this 0086 // here with various small sizes, always forcing in immediate 0087 // re-paint. 0088 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0089 myWidget.show(); 0090 myWidget.resize(QSize()); 0091 myWidget.repaint(); 0092 myWidget.resize(QSize(-1, -1)); 0093 myWidget.repaint(); 0094 myWidget.resize(QSize(-1, 0)); 0095 myWidget.repaint(); 0096 myWidget.resize(QSize(0, -1)); 0097 myWidget.repaint(); 0098 myWidget.resize(QSize(0, 1)); 0099 myWidget.repaint(); 0100 myWidget.resize(QSize(1, 0)); 0101 myWidget.repaint(); 0102 myWidget.resize(QSize(1, 1)); 0103 myWidget.repaint(); 0104 myWidget.resize(QSize(2, 2)); 0105 myWidget.repaint(); 0106 myWidget.resize(QSize(3, 3)); 0107 myWidget.repaint(); 0108 myWidget.resize(QSize(4, 4)); 0109 myWidget.repaint(); 0110 myWidget.resize(QSize(5, 5)); 0111 myWidget.repaint(); 0112 myWidget.resize(QSize(6, 6)); 0113 myWidget.repaint(); 0114 myWidget.resize(QSize(7, 7)); 0115 myWidget.repaint(); 0116 myWidget.resize(QSize(8, 8)); 0117 myWidget.repaint(); 0118 myWidget.resize(QSize(9, 9)); 0119 myWidget.repaint(); 0120 myWidget.resize(QSize(10, 10)); 0121 myWidget.repaint(); 0122 myWidget.resize(QSize(11, 11)); 0123 myWidget.repaint(); 0124 myWidget.resize(QSize(12, 12)); 0125 myWidget.repaint(); 0126 myWidget.resize(QSize(13, 13)); 0127 myWidget.repaint(); 0128 myWidget.resize(QSize(14, 14)); 0129 myWidget.repaint(); 0130 } 0131 0132 void testSetCurrentColorFromWidgetPixelPosition1() 0133 { 0134 // Also very small widget sizes should not crash the widget. 0135 // This might happen because if the widget is too small, there 0136 // is no place for a diagram, and some value conversions are 0137 // diagram-based.. 0138 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0139 const QPoint positive(10, 20); 0140 const QPoint negative(-10, -20); 0141 myWidget.resize(QSize(1, 1)); 0142 // Executing the following lines should not crash! 0143 myWidget.d_pointer->setCurrentColorFromWidgetPixelPosition(positive); 0144 myWidget.d_pointer->setCurrentColorFromWidgetPixelPosition(negative); 0145 } 0146 0147 void testSetCurrentColorFromWidgetPixelPosition2() 0148 { 0149 // Test this function for out-of-gamut positions 0150 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0151 myWidget.show(); 0152 constexpr int size = 100; 0153 myWidget.resize(size, size); 0154 LchDouble color; 0155 0156 // Test for top-left corner 0157 myWidget.d_pointer->setCurrentColorFromWidgetPixelPosition( 0158 // Same x and y spacing from top-left corner 0159 QPoint(size * (-1), size * (-1))); 0160 delayedEventProcessing(); 0161 color = myWidget.currentColor(); 0162 QCOMPARE(color.l, 100); 0163 QCOMPARE(color.c, 0); 0164 0165 // Test for bottom-left corner 0166 myWidget.d_pointer->setCurrentColorFromWidgetPixelPosition( 0167 // Same x and y spacing from bottom-left corner 0168 QPoint(size * (-1), size * 2)); 0169 delayedEventProcessing(); 0170 color = myWidget.currentColor(); 0171 QCOMPARE(color.l, 0); 0172 QCOMPARE(color.c, 0); 0173 0174 // Test for middle-right position 0175 myWidget.d_pointer->setCurrentColorFromWidgetPixelPosition( 0176 // x position far from diagram boundaries, y position in the middle 0177 QPoint(size * 10, size * 50 / 100)); 0178 delayedEventProcessing(); 0179 color = myWidget.currentColor(); 0180 // Lightness should be somewhere in the middle. 0181 QVERIFY(color.l > 10); 0182 QVERIFY(color.l < 90); 0183 // At least 25 should be possible on all hues. 0184 QVERIFY(color.c > 25); 0185 } 0186 0187 void testDefaultBorderPhysical() 0188 { 0189 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0190 QVERIFY(myWidget.d_pointer->defaultBorderPhysical() >= 0); 0191 } 0192 0193 void testLeftBorderPhysical() 0194 { 0195 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0196 QVERIFY(myWidget.d_pointer->defaultBorderPhysical() >= 0); 0197 QVERIFY(myWidget.d_pointer->defaultBorderPhysical() >= myWidget.d_pointer->defaultBorderPhysical()); 0198 } 0199 0200 void testCalculateImageSizePhysical() 0201 { 0202 // Also very small widget sizes should not crash the widget. 0203 // This might happen because of divisions by 0, even when the widget 0204 // is bigger than 0 because of borders or offsets. We test this 0205 // here with various small sizes. 0206 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0207 myWidget.resize(QSize()); 0208 Q_UNUSED(myWidget.d_pointer->calculateImageSizePhysical()); // Should not crash 0209 myWidget.resize(QSize(-1, -1)); 0210 Q_UNUSED(myWidget.d_pointer->calculateImageSizePhysical()); // Should not crash 0211 myWidget.resize(QSize(-1, 0)); 0212 Q_UNUSED(myWidget.d_pointer->calculateImageSizePhysical()); // Should not crash 0213 myWidget.resize(QSize(0, -1)); 0214 Q_UNUSED(myWidget.d_pointer->calculateImageSizePhysical()); // Should not crash 0215 myWidget.resize(QSize(0, 1)); 0216 Q_UNUSED(myWidget.d_pointer->calculateImageSizePhysical()); // Should not crash 0217 myWidget.resize(QSize(1, 0)); 0218 Q_UNUSED(myWidget.d_pointer->calculateImageSizePhysical()); // Should not crash 0219 myWidget.resize(QSize(1, 1)); 0220 Q_UNUSED(myWidget.d_pointer->calculateImageSizePhysical()); // Should not crash 0221 } 0222 0223 void testFromWidgetPixelPositionToColor() 0224 { 0225 // Also very small widget sizes should not crash the widget. 0226 // This might happen because of divisions by 0, even when the widget 0227 // is bigger than 0 because of borders or offsets. We test this 0228 // here with various small sizes. 0229 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0230 const QPoint positive(10, 20); 0231 const QPoint negative(-10, -20); 0232 myWidget.resize(QSize()); 0233 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0234 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0235 myWidget.resize(QSize(-1, -1)); 0236 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0237 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0238 myWidget.resize(QSize(-1, 0)); 0239 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0240 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0241 myWidget.resize(QSize(0, -1)); 0242 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0243 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0244 myWidget.resize(QSize(0, 1)); 0245 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0246 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0247 myWidget.resize(QSize(1, 0)); 0248 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0249 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0250 myWidget.resize(QSize(1, 1)); 0251 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0252 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0253 myWidget.resize(QSize(2, 2)); 0254 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0255 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0256 myWidget.resize(QSize(3, 3)); 0257 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0258 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0259 myWidget.resize(QSize(4, 4)); 0260 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0261 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0262 myWidget.resize(QSize(5, 5)); 0263 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0264 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0265 myWidget.resize(QSize(6, 6)); 0266 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0267 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0268 myWidget.resize(QSize(7, 7)); 0269 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0270 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0271 myWidget.resize(QSize(8, 8)); 0272 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0273 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0274 myWidget.resize(QSize(9, 9)); 0275 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0276 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0277 myWidget.resize(QSize(10, 10)); 0278 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0279 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0280 myWidget.resize(QSize(11, 11)); 0281 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0282 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0283 myWidget.resize(QSize(12, 12)); 0284 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0285 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0286 myWidget.resize(QSize(13, 13)); 0287 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0288 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0289 myWidget.resize(QSize(14, 14)); 0290 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(positive)); 0291 Q_UNUSED(myWidget.d_pointer->fromWidgetPixelPositionToColor(negative)); 0292 } 0293 0294 void testMouseSupport1() 0295 { 0296 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0297 myWidget.show(); 0298 myWidget.resize(2, 2); 0299 // Mouse movements should not crash when the size of the widget is 0300 // too small to show a diagram: 0301 QTest::mousePress(&myWidget, // 0302 Qt::MouseButton::LeftButton, // 0303 Qt::KeyboardModifier::NoModifier, // 0304 QPoint(0, 0)); 0305 // Alternative: Maybe this catches more bugs?…: 0306 // QTest::mouseMove(&myWidget, QPoint(1, 1)); 0307 QTest::mouseRelease(&myWidget, // 0308 Qt::MouseButton::LeftButton, // 0309 Qt::KeyboardModifier::NoModifier, // 0310 QPoint(1, 1)); 0311 } 0312 0313 void testMouseSupport2() 0314 { 0315 // Test reactions to mouse events when moving out-of-gamut 0316 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0317 myWidget.show(); 0318 constexpr int size = 100; 0319 myWidget.resize(size, size); 0320 LchDouble color; 0321 0322 // Test for top-left corner 0323 QTest::mousePress(&myWidget, 0324 Qt::MouseButton::LeftButton, 0325 Qt::KeyboardModifier::NoModifier, 0326 // Press the mouse at a point with some chroma 0327 // (10%) and a medium lightness (50%). This makes 0328 // sure to get a point within the gamut. 0329 QPoint(size * 10 / 100, size * 50 / 100)); 0330 QTest::mouseRelease(&myWidget, 0331 Qt::MouseButton::LeftButton, 0332 Qt::KeyboardModifier::NoModifier, 0333 // Press the mouse at a point with some chroma 0334 // (10%) and a medium lightness (50%). This makes 0335 // sure to get a point within the gamut. 0336 QPoint(size * (-1), size * (-1))); 0337 // Test if the widget value is really the nearest in-gamut color 0338 delayedEventProcessing(); 0339 color = myWidget.currentColor(); 0340 QCOMPARE(color.l, 100); 0341 QCOMPARE(color.c, 0); 0342 0343 // Test for bottom-left corner 0344 QTest::mousePress(&myWidget, 0345 Qt::MouseButton::LeftButton, 0346 Qt::KeyboardModifier::NoModifier, 0347 // Press the mouse at a point with some chroma 0348 // (10%) and a medium lightness (50%). This makes 0349 // sure to get a point within the gamut. 0350 QPoint(size * 10 / 100, size * 50 / 100)); 0351 QTest::mouseRelease(&myWidget, 0352 Qt::MouseButton::LeftButton, 0353 Qt::KeyboardModifier::NoModifier, 0354 // Press the mouse at a point with some chroma 0355 // (10%) and a medium lightness (50%). This makes 0356 // sure to get a point within the gamut. 0357 QPoint(size * (-1), size * 2)); 0358 // Test if the widget value is really the nearest in-gamut color 0359 color = myWidget.currentColor(); 0360 QCOMPARE(color.l, 0); 0361 QCOMPARE(color.c, 0); 0362 0363 // Test for middle-right position 0364 QTest::mousePress(&myWidget, 0365 Qt::MouseButton::LeftButton, 0366 Qt::KeyboardModifier::NoModifier, 0367 // Press the mouse at a point with some chroma 0368 // (10%) and a medium lightness (50%). This makes 0369 // sure to get a point within the gamut. 0370 QPoint(size * 10 / 100, size * 50 / 100)); 0371 QTest::mouseRelease(&myWidget, 0372 Qt::MouseButton::LeftButton, 0373 Qt::KeyboardModifier::NoModifier, 0374 // Press the mouse at a point with some chroma 0375 // (10%) and a medium lightness (50%). This makes 0376 // sure to get a point within the gamut. 0377 QPoint(size * 10, size * 50 / 100)); 0378 // Test if the widget value is really the nearest in-gamut color 0379 color = myWidget.currentColor(); 0380 // Lightness should be somewhere in the middle. 0381 QVERIFY(color.l > 10); 0382 QVERIFY(color.l < 90); 0383 // At least 25 should be possible on all hues. 0384 QVERIFY(color.c > 25); 0385 } 0386 0387 void testPaintEventNormalSize() 0388 { 0389 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0390 myWidget.show(); 0391 // Test normal size 0392 myWidget.resize(100, 100); 0393 myWidget.repaint(); 0394 } 0395 0396 void testPaintEventTooSmallSize() 0397 { 0398 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0399 myWidget.show(); 0400 // Test small size (too small to show a diagram) 0401 myWidget.resize(2, 2); 0402 myWidget.repaint(); 0403 } 0404 0405 void testPaintEventEmptySize() 0406 { 0407 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0408 myWidget.show(); 0409 // Test empty size 0410 myWidget.resize(0, 0); 0411 myWidget.repaint(); 0412 } 0413 0414 void testKeyPressEvent() 0415 { 0416 ChromaLightnessDiagram myDiagram(m_rgbColorSpace); 0417 LchDouble referenceColorLch; 0418 referenceColorLch.l = 50; 0419 referenceColorLch.c = 20; 0420 referenceColorLch.h = 180; 0421 myDiagram.setCurrentColor(referenceColorLch); 0422 0423 // Assert pre-conditions: 0424 0425 QCOMPARE(myDiagram.currentColor().l, 50); 0426 QCOMPARE(myDiagram.currentColor().c, 20); 0427 QCOMPARE(myDiagram.currentColor().h, 180); 0428 0429 // Actual test: 0430 0431 myDiagram.setCurrentColor(referenceColorLch); 0432 QTest::keyClick(&myDiagram, Qt::Key_Left); 0433 QVERIFY(myDiagram.currentColor().l == referenceColorLch.l); 0434 QVERIFY(myDiagram.currentColor().c < referenceColorLch.c); 0435 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0436 0437 myDiagram.setCurrentColor(referenceColorLch); 0438 QTest::keyClick(&myDiagram, Qt::Key_Right); 0439 QVERIFY(myDiagram.currentColor().l == referenceColorLch.l); 0440 QVERIFY(myDiagram.currentColor().c > referenceColorLch.c); 0441 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0442 0443 myDiagram.setCurrentColor(referenceColorLch); 0444 QTest::keyClick(&myDiagram, Qt::Key_Up); 0445 QVERIFY(myDiagram.currentColor().l > referenceColorLch.l); 0446 QVERIFY(myDiagram.currentColor().c == referenceColorLch.c); 0447 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0448 0449 myDiagram.setCurrentColor(referenceColorLch); 0450 QTest::keyClick(&myDiagram, Qt::Key_Down); 0451 QVERIFY(myDiagram.currentColor().l < referenceColorLch.l); 0452 QVERIFY(myDiagram.currentColor().c == referenceColorLch.c); 0453 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0454 0455 myDiagram.setCurrentColor(referenceColorLch); 0456 QTest::keyClick(&myDiagram, Qt::Key_Home); 0457 QVERIFY(myDiagram.currentColor().l == referenceColorLch.l); 0458 QVERIFY(myDiagram.currentColor().c > referenceColorLch.c); 0459 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0460 0461 myDiagram.setCurrentColor(referenceColorLch); 0462 QTest::keyClick(&myDiagram, Qt::Key_End); 0463 QVERIFY(myDiagram.currentColor().l == referenceColorLch.l); 0464 QVERIFY(myDiagram.currentColor().c < referenceColorLch.c); 0465 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0466 0467 myDiagram.setCurrentColor(referenceColorLch); 0468 QTest::keyClick(&myDiagram, Qt::Key_PageUp); 0469 QVERIFY(myDiagram.currentColor().l > referenceColorLch.l); 0470 QVERIFY(myDiagram.currentColor().c == referenceColorLch.c); 0471 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0472 0473 myDiagram.setCurrentColor(referenceColorLch); 0474 QTest::keyClick(&myDiagram, Qt::Key_PageDown); 0475 QVERIFY(myDiagram.currentColor().l < referenceColorLch.l); 0476 QVERIFY(myDiagram.currentColor().c == referenceColorLch.c); 0477 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0478 0479 referenceColorLch.c = 0; 0480 0481 // Chroma should never become negative 0482 0483 myDiagram.setCurrentColor(referenceColorLch); 0484 QTest::keyClick(&myDiagram, Qt::Key_Left); 0485 QCOMPARE(myDiagram.currentColor().l, referenceColorLch.l); 0486 QCOMPARE(myDiagram.currentColor().c, referenceColorLch.c); 0487 QCOMPARE(myDiagram.currentColor().h, referenceColorLch.h); 0488 0489 myDiagram.setCurrentColor(referenceColorLch); 0490 QTest::keyClick(&myDiagram, Qt::Key_End); 0491 QCOMPARE(myDiagram.currentColor().l, referenceColorLch.l); 0492 QCOMPARE(myDiagram.currentColor().c, referenceColorLch.c); 0493 QCOMPARE(myDiagram.currentColor().h, referenceColorLch.h); 0494 0495 referenceColorLch.l = 0; 0496 0497 // Lightness should never be smaller than 0. 0498 0499 myDiagram.setCurrentColor(referenceColorLch); 0500 QTest::keyClick(&myDiagram, Qt::Key_Down); 0501 QVERIFY(myDiagram.currentColor().l >= 0); 0502 QVERIFY(myDiagram.currentColor().c == referenceColorLch.c); 0503 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0504 0505 myDiagram.setCurrentColor(referenceColorLch); 0506 QTest::keyClick(&myDiagram, Qt::Key_PageDown); 0507 QVERIFY(myDiagram.currentColor().l >= 0); 0508 QVERIFY(myDiagram.currentColor().c == referenceColorLch.c); 0509 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0510 0511 referenceColorLch.l = 100; 0512 0513 // Lightness should never be bigger than 100. 0514 0515 myDiagram.setCurrentColor(referenceColorLch); 0516 QTest::keyClick(&myDiagram, Qt::Key_Up); 0517 QVERIFY(myDiagram.currentColor().l <= 100); 0518 QVERIFY(myDiagram.currentColor().c == referenceColorLch.c); 0519 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0520 0521 myDiagram.setCurrentColor(referenceColorLch); 0522 QTest::keyClick(&myDiagram, Qt::Key_PageUp); 0523 QVERIFY(myDiagram.currentColor().l <= 100); 0524 QVERIFY(myDiagram.currentColor().c == referenceColorLch.c); 0525 QVERIFY(myDiagram.currentColor().h == referenceColorLch.h); 0526 } 0527 0528 void testIsWidgetPixelPositionInGamut() 0529 { 0530 ChromaLightnessDiagram myDiagram(m_rgbColorSpace); 0531 myDiagram.show(); 0532 myDiagram.resize(QSize(2, 2)); 0533 // On very small widget sizes, no diagram is visible. Therefore, 0534 // no pixel should be in-gamut. 0535 QVERIFY(!myDiagram.d_pointer->isWidgetPixelPositionInGamut(QPoint(0, 0))); 0536 QVERIFY(!myDiagram.d_pointer->isWidgetPixelPositionInGamut(QPoint(0, 1))); 0537 QVERIFY(!myDiagram.d_pointer->isWidgetPixelPositionInGamut(QPoint(0, 2))); 0538 QVERIFY(!myDiagram.d_pointer->isWidgetPixelPositionInGamut(QPoint(1, 0))); 0539 QVERIFY(!myDiagram.d_pointer->isWidgetPixelPositionInGamut(QPoint(1, 1))); 0540 QVERIFY(!myDiagram.d_pointer->isWidgetPixelPositionInGamut(QPoint(1, 2))); 0541 QVERIFY(!myDiagram.d_pointer->isWidgetPixelPositionInGamut(QPoint(2, 0))); 0542 QVERIFY(!myDiagram.d_pointer->isWidgetPixelPositionInGamut(QPoint(2, 1))); 0543 QVERIFY(!myDiagram.d_pointer->isWidgetPixelPositionInGamut(QPoint(2, 2))); 0544 } 0545 0546 #ifndef MSVC_DLL 0547 // The automatic export of otherwise private symbols on MSVC 0548 // shared libraries via CMake's WINDOWS_EXPORT_ALL_SYMBOLS property 0549 // does not work well for Qt meta objects, resulting in non-functional 0550 // signals. Since the following unit tests require signals, it cannot be 0551 // built for MSVC shared libraries. 0552 0553 void testCurrentColorProperty() 0554 { 0555 ChromaLightnessDiagram test{m_rgbColorSpace}; 0556 LchDouble color; 0557 color.l = 50; 0558 color.c = 20; 0559 color.h = 10; 0560 test.setCurrentColor(color); 0561 QVERIFY(test.currentColor().hasSameCoordinates(color)); 0562 QSignalSpy spy(&test, &ChromaLightnessDiagram::currentColorChanged); 0563 QCOMPARE(spy.count(), 0); 0564 0565 // Change hue only: 0566 color.h += 1; 0567 test.setCurrentColor(color); 0568 QCOMPARE(spy.count(), 1); 0569 QVERIFY(test.currentColor().hasSameCoordinates(color)); 0570 0571 // Change chroma only: 0572 color.c += 1; 0573 test.setCurrentColor(color); 0574 QCOMPARE(spy.count(), 2); 0575 QVERIFY(test.currentColor().hasSameCoordinates(color)); 0576 0577 // Change lightness only: 0578 color.l += 1; 0579 test.setCurrentColor(color); 0580 QCOMPARE(spy.count(), 3); 0581 QVERIFY(test.currentColor().hasSameCoordinates(color)); 0582 0583 // Not changing the color should not trigger the signal 0584 test.setCurrentColor(color); 0585 QCOMPARE(spy.count(), 3); 0586 QVERIFY(test.currentColor().hasSameCoordinates(color)); 0587 } 0588 0589 #endif 0590 0591 void testResizeEvent() 0592 { 0593 ChromaLightnessDiagram test{m_rgbColorSpace}; 0594 test.show(); 0595 // Resize events should not crash! 0596 test.resize(QSize(100, 100)); // normal size 0597 test.resize(QSize(2, 2)); // very small size 0598 test.resize(QSize(0, 0)); // empty size 0599 test.resize(QSize(-1, -1)); // invalid size 0600 } 0601 0602 void testSizeHintAndMinimumSizeHint() 0603 { 0604 ChromaLightnessDiagram test{m_rgbColorSpace}; 0605 test.show(); 0606 QVERIFY(test.minimumSizeHint().width() >= 0); 0607 QVERIFY(test.minimumSizeHint().height() >= 0); 0608 QVERIFY(test.sizeHint().width() >= test.minimumSizeHint().width()); 0609 QVERIFY(test.sizeHint().height() >= test.minimumSizeHint().height()); 0610 } 0611 0612 void testOutOfGamutColors() 0613 { 0614 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0615 myWidget.show(); 0616 myWidget.resize(QSize(400, 400)); 0617 0618 // Test that setting out-of-gamut colors works 0619 0620 const LchDouble myFirstColor{100, 150, 0}; 0621 myWidget.setCurrentColor(myFirstColor); 0622 QVERIFY(myFirstColor.hasSameCoordinates(myWidget.currentColor())); 0623 QVERIFY( // 0624 myFirstColor.hasSameCoordinates(myWidget.d_pointer->m_currentColor)); 0625 0626 const LchDouble mySecondColor{0, 150, 0}; 0627 myWidget.setCurrentColor(mySecondColor); 0628 QVERIFY(mySecondColor.hasSameCoordinates(myWidget.currentColor())); 0629 QVERIFY( // 0630 mySecondColor.hasSameCoordinates(myWidget.d_pointer->m_currentColor)); 0631 } 0632 0633 void testOutOfRange() 0634 { 0635 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0636 myWidget.show(); 0637 myWidget.resize(QSize(400, 400)); 0638 0639 // Test that setting colors, that are not only out-of-gamut colors 0640 // but also out of a reasonable range, works. 0641 0642 const LchDouble myFirstColor{300, 550, -10}; 0643 myWidget.setCurrentColor(myFirstColor); 0644 QVERIFY( // 0645 myFirstColor.hasSameCoordinates(myWidget.currentColor())); 0646 QVERIFY( // 0647 myFirstColor.hasSameCoordinates(myWidget.d_pointer->m_currentColor)); 0648 0649 const LchDouble mySecondColor{-100, -150, 890}; 0650 myWidget.setCurrentColor(mySecondColor); 0651 QVERIFY(mySecondColor.hasSameCoordinates(myWidget.currentColor())); 0652 QVERIFY(mySecondColor.hasSameCoordinates(myWidget.d_pointer->m_currentColor)); 0653 } 0654 0655 void testNearestInGamutColorByAdjustingChromaLightness() 0656 { 0657 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0658 0659 // Variables 0660 LchDouble color; 0661 LchDouble nearestInGamutColor; 0662 0663 // In-gamut colors should not be changed. 0664 color.l = 50; 0665 color.c = 20; 0666 color.h = 10; 0667 myWidget.setCurrentColor(color); 0668 nearestInGamutColor = // 0669 myWidget.d_pointer->nearestInGamutColorByAdjustingChromaLightness(color.c, color.l); 0670 QVERIFY(nearestInGamutColor.hasSameCoordinates(color)); 0671 0672 // A negative chroma value should not be normalized (this would 0673 // mean to change the hue), but just put to 0. 0674 color.l = 50; 0675 color.c = -20; 0676 color.h = 10; 0677 myWidget.setCurrentColor(color); 0678 nearestInGamutColor = // 0679 myWidget.d_pointer->nearestInGamutColorByAdjustingChromaLightness(color.c, color.l); 0680 QCOMPARE(nearestInGamutColor.l, 50); 0681 QCOMPARE(nearestInGamutColor.c, 0); 0682 QCOMPARE(nearestInGamutColor.h, 10); 0683 } 0684 0685 void testNearestInGamutColorByAdjustingChromaLightnessSmallSize() 0686 { 0687 ChromaLightnessDiagram myWidget{m_rgbColorSpace}; 0688 0689 // Variables 0690 LchDouble color; 0691 LchDouble nearestInGamutColor; 0692 0693 // In-gamut colors should not be changed. 0694 color.l = 50; 0695 color.c = 20; 0696 color.h = 10; 0697 myWidget.setCurrentColor(color); 0698 0699 // nearestInGamutColorByAdjustingChromaLightness() is only 0700 // guaranteed to work correctly for an image size of at least 0701 // two pixel width and two pixel height. Test here if at least 0702 // we can call the function without crash, even if the result 0703 // does not make sense. 0704 myWidget.resize(1, 1); 0705 nearestInGamutColor = // 0706 myWidget.d_pointer->nearestInGamutColorByAdjustingChromaLightness(color.c, color.l); 0707 } 0708 0709 void testDistanceFromRange() 0710 { 0711 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(1, 2, 3), 0); 0712 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(-5, -4, -3), 0); 0713 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(-1, 0, 1), 0); 0714 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 6, 7), 0); 0715 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 5, 7), 0); 0716 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 7, 7), 0); 0717 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 4, 7), 1); 0718 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 3, 7), 2); 0719 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 8, 7), 1); 0720 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 9, 7), 2); 0721 0722 // Special case: low == hight 0723 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 5, 5), 0); 0724 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 4, 5), 1); 0725 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 3, 5), 2); 0726 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 6, 5), 1); 0727 QCOMPARE(ChromaLightnessDiagramPrivate::distanceFromRange(5, 7, 5), 2); 0728 0729 // Special cases for floating point operations 0730 if constexpr (std::numeric_limits<double>::has_infinity // 0731 && std::numeric_limits<double>::has_signaling_NaN // 0732 && std::numeric_limits<double>::has_quiet_NaN // 0733 ) { 0734 constexpr auto inf = std::numeric_limits<double>::infinity(); 0735 constexpr auto qnan = std::numeric_limits<double>::quiet_NaN(); 0736 constexpr auto snan = std::numeric_limits<double>::signaling_NaN(); 0737 0738 // Infinity 0739 QCOMPARE( // 0740 ChromaLightnessDiagramPrivate::distanceFromRange(-inf, 7., 5.), 0741 2); 0742 QCOMPARE( // 0743 ChromaLightnessDiagramPrivate::distanceFromRange(-inf, 5., 5.), 0744 0); 0745 QCOMPARE( // 0746 ChromaLightnessDiagramPrivate::distanceFromRange(-inf, 3., 5.), 0747 0); 0748 QCOMPARE( // 0749 ChromaLightnessDiagramPrivate::distanceFromRange(3., -inf, 5.), 0750 inf); 0751 QCOMPARE( // 0752 ChromaLightnessDiagramPrivate::distanceFromRange(3., inf, 5.), 0753 inf); 0754 QCOMPARE( // 0755 ChromaLightnessDiagramPrivate::distanceFromRange(3., 5., inf), 0756 0); 0757 QCOMPARE( // 0758 ChromaLightnessDiagramPrivate::distanceFromRange(3., 3., inf), 0759 0); 0760 QCOMPARE( // 0761 ChromaLightnessDiagramPrivate::distanceFromRange(3., 1., inf), 0762 2); 0763 0764 // Nan 0765 QVERIFY(std::isnan( // 0766 ChromaLightnessDiagramPrivate::distanceFromRange(qnan, 2., 3.))); 0767 QVERIFY(std::isnan( // 0768 ChromaLightnessDiagramPrivate::distanceFromRange(1., qnan, 3.))); 0769 QVERIFY(std::isnan( // 0770 ChromaLightnessDiagramPrivate::distanceFromRange(1., 2., qnan))); 0771 QVERIFY(std::isnan( // 0772 ChromaLightnessDiagramPrivate::distanceFromRange(qnan, qnan, 3.))); 0773 QVERIFY(std::isnan( // 0774 ChromaLightnessDiagramPrivate::distanceFromRange(qnan, 2., qnan))); 0775 QVERIFY(std::isnan( // 0776 ChromaLightnessDiagramPrivate::distanceFromRange(1., qnan, qnan))); 0777 QVERIFY(std::isnan( // 0778 ChromaLightnessDiagramPrivate::distanceFromRange(qnan, qnan, qnan))); 0779 QVERIFY(std::isnan( // 0780 ChromaLightnessDiagramPrivate::distanceFromRange(snan, snan, snan))); 0781 } 0782 } 0783 0784 void testNearestNeighborSearch() 0785 { 0786 // Setup 0787 const auto doesExist = [](const QPoint point) -> bool { 0788 // Our valid search rectangle is from (2, 2) to (8, 8). 0789 if (isInRange(-2, point.x(), 8) && isInRange(-2, point.y(), 8)) { 0790 QList<QPoint> existingPoints({// 0791 QPoint(-2, -2), 0792 QPoint(5, 5), 0793 QPoint(8, 8)}); 0794 return existingPoints.contains(point); 0795 } 0796 // A correct implementation of nearestNeighborSearch should never 0797 // call the callback function with values outside the valid range, 0798 // so we should never get here: 0799 return true; 0800 }; 0801 constexpr auto searchRectangle = QRect(QPoint(-2, -2), QSize(11, 11)); 0802 QVERIFY(searchRectangle.contains(QPoint(-3, -3)) == false); // assert 0803 QVERIFY(searchRectangle.contains(QPoint(-2, -2))); // assert 0804 QVERIFY(searchRectangle.contains(QPoint(8, 8))); // assert 0805 QVERIFY(searchRectangle.contains(QPoint(9, 9)) == false); // assert 0806 0807 // Actual tests 0808 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(-2, -2), searchRectangle, doesExist), // 0809 QPoint(-2, -2)); 0810 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(-1, -2), searchRectangle, doesExist), // 0811 QPoint(-2, -2)); 0812 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(-2, -1), searchRectangle, doesExist), // 0813 QPoint(-2, -2)); 0814 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(-3, -2), searchRectangle, doesExist), // 0815 QPoint(-2, -2)); 0816 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(-2, -3), searchRectangle, doesExist), // 0817 QPoint(-2, -2)); 0818 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(-3, -3), searchRectangle, doesExist), // 0819 QPoint(-2, -2)); 0820 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(1, 1), searchRectangle, doesExist), // 0821 QPoint(-2, -2)); 0822 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(4, 4), searchRectangle, doesExist), // 0823 QPoint(5, 5)); 0824 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(5, 5), searchRectangle, doesExist), // 0825 QPoint(5, 5)); 0826 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(-100, 5), searchRectangle, doesExist), // 0827 QPoint(-2, -2)); 0828 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(-100, -100), searchRectangle, doesExist), // 0829 QPoint(-2, -2)); 0830 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(100, 100), searchRectangle, doesExist), // 0831 QPoint(8, 8)); 0832 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(7, 100), searchRectangle, doesExist), // 0833 QPoint(8, 8)); 0834 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(100, 7), searchRectangle, doesExist), // 0835 QPoint(8, 8)); 0836 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(-2, 8), searchRectangle, doesExist), // 0837 QPoint(5, 5)); 0838 QCOMPARE(ChromaLightnessDiagramPrivate::nearestNeighborSearch(QPoint(8, -2), searchRectangle, doesExist), // 0839 QPoint(5, 5)); 0840 } 0841 }; 0842 0843 } // namespace PerceptualColor 0844 0845 QTEST_MAIN(PerceptualColor::TestChromaLightnessDiagram) 0846 0847 // The following “include” is necessary because we do not use a header file: 0848 #include "testchromalightnessdiagram.moc"