File indexing completed on 2025-02-16 10:41:56
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 "chromahuediagram.h" 0007 // Second, the private implementation. 0008 #include "chromahuediagram_p.h" // IWYU pragma: keep 0009 0010 #include "constpropagatinguniquepointer.h" 0011 #include "lchdouble.h" 0012 #include "polarpointf.h" 0013 #include "rgbcolorspacefactory.h" 0014 #include <lcms2.h> 0015 #include <qcoreevent.h> 0016 #include <qevent.h> 0017 #include <qglobal.h> 0018 #include <qnamespace.h> 0019 #include <qobject.h> 0020 #include <qpoint.h> 0021 #include <qscopedpointer.h> 0022 #include <qsharedpointer.h> 0023 #include <qsignalspy.h> 0024 #include <qsize.h> 0025 #include <qtest.h> 0026 #include <qtestcase.h> 0027 0028 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0029 #include <qtmetamacros.h> 0030 #else 0031 #include <qobjectdefs.h> 0032 #include <qstring.h> 0033 #endif 0034 0035 static void snippet01() 0036 { 0037 //! [instantiate] 0038 auto myColorSpace = PerceptualColor::RgbColorSpaceFactory::createSrgb(); 0039 PerceptualColor::ChromaHueDiagram *myDiagram = new PerceptualColor::ChromaHueDiagram(myColorSpace); 0040 PerceptualColor::LchDouble myColor; 0041 myColor.h = 270; 0042 myColor.l = 50; 0043 myColor.c = 25; 0044 myDiagram->setCurrentColor(myColor); 0045 myDiagram->show(); 0046 //! [instantiate] 0047 delete myDiagram; 0048 } 0049 0050 namespace PerceptualColor 0051 { 0052 class RgbColorSpace; 0053 0054 class TestChromaHueDiagram : public QObject 0055 { 0056 Q_OBJECT 0057 0058 public: 0059 explicit TestChromaHueDiagram(QObject *parent = nullptr) 0060 : QObject(parent) 0061 { 0062 } 0063 0064 private: 0065 QSharedPointer<PerceptualColor::RgbColorSpace> m_rgbColorSpace = RgbColorSpaceFactory::createSrgb(); 0066 0067 bool isEqual(const LchDouble &first, const LchDouble &second) 0068 { 0069 return ((first.l == second.l) && (first.c == second.c) && (first.h == second.h)); 0070 } 0071 0072 private Q_SLOTS: 0073 void initTestCase() 0074 { 0075 // Called before the first test function is executed 0076 } 0077 0078 void cleanupTestCase() 0079 { 0080 // Called after the last test function was executed 0081 } 0082 0083 void init() 0084 { 0085 // Called before each test function is executed 0086 } 0087 0088 void cleanup() 0089 { 0090 // Called after every test function 0091 } 0092 0093 void testConstructorAndDestructor() 0094 { 0095 PerceptualColor::ChromaHueDiagram myDiagram(m_rgbColorSpace); 0096 } 0097 0098 void testShow() 0099 { 0100 PerceptualColor::ChromaHueDiagram myDiagram(m_rgbColorSpace); 0101 myDiagram.show(); 0102 } 0103 0104 void testKeyPressEvent() 0105 { 0106 PerceptualColor::ChromaHueDiagram myDiagram(m_rgbColorSpace); 0107 LchDouble referenceColorLch; 0108 referenceColorLch.l = 50; 0109 referenceColorLch.c = 0; 0110 referenceColorLch.h = 180; 0111 myDiagram.setCurrentColor(referenceColorLch); 0112 if (myDiagram.currentColor().h != 180) { 0113 throw 0; 0114 } 0115 if (myDiagram.currentColor().c != 0) { 0116 throw 0; 0117 } 0118 LchDouble referenceColorChromaLch; 0119 referenceColorChromaLch.l = 50; 0120 referenceColorChromaLch.c = 10; 0121 referenceColorChromaLch.h = 180; 0122 myDiagram.setCurrentColor(referenceColorChromaLch); 0123 0124 QScopedPointer<QKeyEvent> myEvent; // TODO Use QTest::keyClick() instead! 0125 myEvent.reset(new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier)); 0126 0127 myDiagram.setCurrentColor(referenceColorChromaLch); 0128 myEvent.reset(new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier)); 0129 myDiagram.keyPressEvent(myEvent.data()); 0130 QVERIFY2(myDiagram.currentColor().c > 0, "Test Key_Up"); 0131 0132 myDiagram.setCurrentColor(referenceColorChromaLch); 0133 myEvent.reset(new QKeyEvent(QEvent::KeyPress, Qt::Key_PageUp, Qt::NoModifier)); 0134 myDiagram.keyPressEvent(myEvent.data()); 0135 QVERIFY2(myDiagram.currentColor().c > 0, "Test Key_PageUp"); 0136 0137 myDiagram.setCurrentColor(referenceColorChromaLch); 0138 myEvent.reset(new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier)); 0139 myDiagram.keyPressEvent(myEvent.data()); 0140 QVERIFY2(myDiagram.currentColor().c < 10, "Test Key_Down"); 0141 0142 myDiagram.setCurrentColor(referenceColorChromaLch); 0143 myEvent.reset(new QKeyEvent(QEvent::KeyPress, Qt::Key_PageDown, Qt::NoModifier)); 0144 myDiagram.keyPressEvent(myEvent.data()); 0145 QVERIFY2(myDiagram.currentColor().c < 10, "Test Key_PageDown"); 0146 0147 myDiagram.setCurrentColor(referenceColorChromaLch); 0148 myEvent.reset(new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier)); 0149 myDiagram.keyPressEvent(myEvent.data()); 0150 QVERIFY2(myDiagram.currentColor().c >= 0, "Test Key_Down never negative"); 0151 0152 myDiagram.setCurrentColor(referenceColorChromaLch); 0153 myEvent.reset(new QKeyEvent(QEvent::KeyPress, Qt::Key_PageDown, Qt::NoModifier)); 0154 myDiagram.keyPressEvent(myEvent.data()); 0155 QVERIFY2(myDiagram.currentColor().c >= 0, "Test Key_PageDown never negative"); 0156 0157 myDiagram.setCurrentColor(referenceColorChromaLch); 0158 myEvent.reset(new QKeyEvent(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier)); 0159 myDiagram.keyPressEvent(myEvent.data()); 0160 QVERIFY2(myDiagram.currentColor().h > 180, "Test Key_Left"); 0161 0162 myDiagram.setCurrentColor(referenceColorChromaLch); 0163 myEvent.reset(new QKeyEvent(QEvent::KeyPress, Qt::Key_Home, Qt::NoModifier)); 0164 myDiagram.keyPressEvent(myEvent.data()); 0165 QVERIFY2(myDiagram.currentColor().h > 180, "Test Key_Home"); 0166 0167 myDiagram.setCurrentColor(referenceColorChromaLch); 0168 myEvent.reset(new QKeyEvent(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier)); 0169 myDiagram.keyPressEvent(myEvent.data()); 0170 QVERIFY2(myDiagram.currentColor().h < 180, "Test Key_Right"); 0171 0172 myDiagram.setCurrentColor(referenceColorChromaLch); 0173 myEvent.reset(new QKeyEvent(QEvent::KeyPress, Qt::Key_End, Qt::NoModifier)); 0174 myDiagram.keyPressEvent(myEvent.data()); 0175 QVERIFY2(myDiagram.currentColor().h < 180, "Test Key_End"); 0176 } 0177 0178 void testMinimumSizeHint() 0179 { 0180 PerceptualColor::ChromaHueDiagram myDiagram(m_rgbColorSpace); 0181 QVERIFY2(myDiagram.minimumSizeHint().width() > 0, "minimalSizeHint width is implemented."); 0182 QVERIFY2(myDiagram.minimumSizeHint().height() > 0, "minimalSizeHint height is implemented."); 0183 // Check that the hint is a square: 0184 QCOMPARE(myDiagram.minimumSizeHint().width(), myDiagram.minimumSizeHint().height()); 0185 } 0186 0187 void testSizeHint() 0188 { 0189 PerceptualColor::ChromaHueDiagram myDiagram(m_rgbColorSpace); 0190 QVERIFY2(myDiagram.sizeHint().width() > myDiagram.minimumSizeHint().width(), "sizeHint width is bigger than minimalSizeHint width."); 0191 QVERIFY2(myDiagram.sizeHint().height() > myDiagram.minimumSizeHint().height(), "sizeHint height is bigger than minimalSizeHint height."); 0192 // Check that the hint is a square: 0193 QCOMPARE(myDiagram.minimumSizeHint().width(), myDiagram.minimumSizeHint().height()); 0194 } 0195 0196 void testColorProperty() 0197 { 0198 PerceptualColor::ChromaHueDiagram myDiagram(m_rgbColorSpace); 0199 QSignalSpy mySpy(&myDiagram, &PerceptualColor::ChromaHueDiagram::currentColorChanged); 0200 LchDouble referenceColorLch; 0201 referenceColorLch.l = 50; 0202 referenceColorLch.c = 10; 0203 referenceColorLch.h = 180; 0204 0205 // Test if signal for new color is emitted. 0206 myDiagram.setCurrentColor(referenceColorLch); 0207 QCOMPARE(mySpy.count(), 1); 0208 QVERIFY2(isEqual(myDiagram.currentColor(), referenceColorLch), // 0209 "Verify that the color is equal to the reference color."); 0210 0211 // Test that no signal is emitted for old color. 0212 myDiagram.setCurrentColor(referenceColorLch); 0213 QCOMPARE(mySpy.count(), 1); 0214 QVERIFY2(isEqual(myDiagram.currentColor(), referenceColorLch), // 0215 "Verify that the color is equal to the reference color."); 0216 } 0217 0218 void testDiagramOffset() 0219 { 0220 PerceptualColor::ChromaHueDiagram myDiagram(m_rgbColorSpace); 0221 myDiagram.show(); // Necessary to allow event processing 0222 myDiagram.resize(50, 50); 0223 qreal oldOffset = myDiagram.d_pointer->diagramOffset(); 0224 myDiagram.resize(100, 100); 0225 QVERIFY2(myDiagram.d_pointer->diagramOffset() > oldOffset, 0226 "Verify that the offset at widget size 150 is bigger " 0227 "than at widget size 100."); 0228 } 0229 0230 void testdiagramCenter() 0231 { 0232 PerceptualColor::ChromaHueDiagram myDiagram(m_rgbColorSpace); 0233 myDiagram.resize(100, 100); 0234 // Test conformance with diagramOffset() 0235 QCOMPARE(myDiagram.d_pointer->diagramCenter().x(), // 0236 myDiagram.d_pointer->diagramOffset()); 0237 QCOMPARE(myDiagram.d_pointer->diagramCenter().y(), // 0238 myDiagram.d_pointer->diagramOffset()); 0239 } 0240 0241 void testConversions() 0242 { 0243 PerceptualColor::ChromaHueDiagram myDiagram(m_rgbColorSpace); 0244 LchDouble myGrayColor; 0245 myGrayColor.h = 0; 0246 myGrayColor.l = 50; 0247 myGrayColor.c = 0; 0248 myDiagram.setCurrentColor(myGrayColor); 0249 myDiagram.show(); // Necessary to make sure resize events are processed 0250 constexpr int widgetSize = 300; 0251 myDiagram.resize(widgetSize, widgetSize); 0252 QCOMPARE(myDiagram.size(), QSize(widgetSize, widgetSize)); 0253 // Chose a position near to, but different from the center. 0254 constexpr int testPosition = widgetSize / 2 + 10; 0255 myDiagram.d_pointer->setColorFromWidgetPixelPosition( // 0256 QPoint(testPosition, testPosition)); 0257 QCOMPARE( // 0258 myDiagram.d_pointer->m_currentColor.l, // 0259 myDiagram.d_pointer->fromWidgetPixelPositionToLab(QPoint(testPosition, testPosition)).L); 0260 QCOMPARE( // 0261 PolarPointF(myDiagram.d_pointer->m_currentColor.c, myDiagram.d_pointer->m_currentColor.h).toCartesian().x(), // 0262 myDiagram.d_pointer->fromWidgetPixelPositionToLab(QPoint(testPosition, testPosition)).a); 0263 QCOMPARE( // 0264 PolarPointF(myDiagram.d_pointer->m_currentColor.c, myDiagram.d_pointer->m_currentColor.h).toCartesian().y(), 0265 myDiagram.d_pointer->fromWidgetPixelPositionToLab(QPoint(testPosition, testPosition)).b); 0266 QCOMPARE(myDiagram.d_pointer->widgetCoordinatesFromCurrentColor(), // 0267 QPoint(testPosition, testPosition) + QPointF(0.5, 0.5)); 0268 } 0269 0270 void testVerySmallWidgetSizes() 0271 { 0272 // Also very small widget sizes should not crash the widget. 0273 // This might happen because of divisions by 0, even when the widget 0274 // is bigger than 0 because of borders or offsets. We test this 0275 // here with various small sizes, always forcing in immediate 0276 // re-paint. 0277 ChromaHueDiagram myWidget{m_rgbColorSpace}; 0278 myWidget.show(); 0279 myWidget.resize(QSize()); 0280 myWidget.repaint(); 0281 myWidget.resize(QSize(-1, -1)); 0282 myWidget.repaint(); 0283 myWidget.resize(QSize(-1, 0)); 0284 myWidget.repaint(); 0285 myWidget.resize(QSize(0, -1)); 0286 myWidget.repaint(); 0287 myWidget.resize(QSize(0, 1)); 0288 myWidget.repaint(); 0289 myWidget.resize(QSize(1, 0)); 0290 myWidget.repaint(); 0291 myWidget.resize(QSize(1, 1)); 0292 myWidget.repaint(); 0293 myWidget.resize(QSize(2, 2)); 0294 myWidget.repaint(); 0295 myWidget.resize(QSize(3, 3)); 0296 myWidget.repaint(); 0297 myWidget.resize(QSize(4, 4)); 0298 myWidget.repaint(); 0299 myWidget.resize(QSize(5, 5)); 0300 myWidget.repaint(); 0301 myWidget.resize(QSize(6, 6)); 0302 myWidget.repaint(); 0303 myWidget.resize(QSize(7, 7)); 0304 myWidget.repaint(); 0305 myWidget.resize(QSize(8, 8)); 0306 myWidget.repaint(); 0307 myWidget.resize(QSize(9, 9)); 0308 myWidget.repaint(); 0309 myWidget.resize(QSize(10, 10)); 0310 myWidget.repaint(); 0311 myWidget.resize(QSize(11, 11)); 0312 myWidget.repaint(); 0313 myWidget.resize(QSize(12, 12)); 0314 myWidget.repaint(); 0315 myWidget.resize(QSize(13, 13)); 0316 myWidget.repaint(); 0317 myWidget.resize(QSize(14, 14)); 0318 myWidget.repaint(); 0319 } 0320 0321 void testOutOfGamutColors() 0322 { 0323 ChromaHueDiagram myWidget{m_rgbColorSpace}; 0324 myWidget.show(); 0325 myWidget.resize(QSize(400, 400)); 0326 0327 // Test that setting out-of-gamut colors works 0328 0329 const LchDouble myFirstColor{100, 150, 0}; 0330 myWidget.setCurrentColor(myFirstColor); 0331 QVERIFY(myFirstColor.hasSameCoordinates(myWidget.currentColor())); 0332 QVERIFY(myFirstColor.hasSameCoordinates(myWidget.d_pointer->m_currentColor)); 0333 0334 const LchDouble mySecondColor{0, 150, 0}; 0335 myWidget.setCurrentColor(mySecondColor); 0336 QVERIFY(mySecondColor.hasSameCoordinates(myWidget.currentColor())); 0337 QVERIFY(mySecondColor.hasSameCoordinates(myWidget.d_pointer->m_currentColor)); 0338 } 0339 0340 void testOutOfRange() 0341 { 0342 ChromaHueDiagram myWidget{m_rgbColorSpace}; 0343 myWidget.show(); 0344 myWidget.resize(QSize(400, 400)); 0345 0346 // Test that setting colors, that are not only out-of-gamut colors 0347 // but also out of a reasonable range, works. 0348 0349 const LchDouble myFirstColor{300, 550, -10}; 0350 myWidget.setCurrentColor(myFirstColor); 0351 QVERIFY(myFirstColor.hasSameCoordinates(myWidget.currentColor())); 0352 QVERIFY(myFirstColor.hasSameCoordinates(myWidget.d_pointer->m_currentColor)); 0353 0354 const LchDouble mySecondColor{-100, -150, 890}; 0355 myWidget.setCurrentColor(mySecondColor); 0356 QVERIFY(mySecondColor.hasSameCoordinates(myWidget.currentColor())); 0357 QVERIFY(mySecondColor.hasSameCoordinates(myWidget.d_pointer->m_currentColor)); 0358 } 0359 0360 void testSnipped01() 0361 { 0362 snippet01(); 0363 } 0364 }; 0365 0366 } // namespace PerceptualColor 0367 0368 QTEST_MAIN(PerceptualColor::TestChromaHueDiagram) 0369 0370 // The following “include” is necessary because we do not use a header file: 0371 #include "testchromahuediagram.moc"