File indexing completed on 2025-04-27 04:24:08
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 "wheelcolorpicker.h" 0007 // Second, the private implementation. 0008 #include "wheelcolorpicker_p.h" // IWYU pragma: keep 0009 0010 #include "chromalightnessdiagram.h" 0011 #include "colorwheel.h" 0012 #include "constpropagatinguniquepointer.h" 0013 #include "lchdouble.h" 0014 #include "rgbcolorspace.h" 0015 #include "rgbcolorspacefactory.h" 0016 #include <qglobal.h> 0017 #include <qobject.h> 0018 #include <qpointer.h> 0019 #include <qsharedpointer.h> 0020 #include <qsignalspy.h> 0021 #include <qsize.h> 0022 #include <qtest.h> 0023 #include <qtestcase.h> 0024 0025 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0026 #include <qtmetamacros.h> 0027 #else 0028 #include <qobjectdefs.h> 0029 #include <qstring.h> 0030 #endif 0031 0032 namespace PerceptualColor 0033 { 0034 class TestWheelColorPicker : public QObject 0035 { 0036 Q_OBJECT 0037 0038 public: 0039 explicit TestWheelColorPicker(QObject *parent = nullptr) 0040 : QObject(parent) 0041 { 0042 } 0043 0044 private: 0045 QSharedPointer<RgbColorSpace> m_rgbColorSpace = RgbColorSpaceFactory::createSrgb(); 0046 0047 private Q_SLOTS: 0048 void initTestCase() 0049 { 0050 // Called before the first test function is executed 0051 } 0052 0053 void cleanupTestCase() 0054 { 0055 // Called after the last test function was executed 0056 } 0057 0058 void init() 0059 { 0060 // Called before each test function is executed 0061 } 0062 0063 void cleanup() 0064 { 0065 // Called after every test function 0066 } 0067 0068 void testConstructorDestructor() 0069 { 0070 // Test for crash in constructor or destructor 0071 WheelColorPicker test{m_rgbColorSpace}; 0072 } 0073 0074 void testCurrentColorProperty() 0075 { 0076 WheelColorPicker test{m_rgbColorSpace}; 0077 LchDouble color; 0078 color.l = 50; 0079 color.c = 20; 0080 color.h = 10; 0081 test.setCurrentColor(color); 0082 QSignalSpy spy(&test, &WheelColorPicker::currentColorChanged); 0083 QCOMPARE(spy.count(), 0); 0084 0085 // Change hue only: 0086 color.h += 1; 0087 test.setCurrentColor(color); 0088 QCOMPARE(spy.count(), 1); 0089 QCOMPARE(test.d_pointer->m_chromaLightnessDiagram->currentColor().h, color.h); 0090 QCOMPARE(test.d_pointer->m_colorWheel->hue(), color.h); 0091 0092 // Change chroma only: 0093 color.c += 1; 0094 test.setCurrentColor(color); 0095 QCOMPARE(spy.count(), 2); 0096 QCOMPARE(test.d_pointer->m_chromaLightnessDiagram->currentColor().c, color.c); 0097 QCOMPARE(test.d_pointer->m_colorWheel->hue(), color.h); 0098 0099 // Not changing the color should not trigger the signal 0100 test.setCurrentColor(color); 0101 QCOMPARE(spy.count(), 2); 0102 QCOMPARE(test.d_pointer->m_chromaLightnessDiagram->currentColor().c, color.c); 0103 QCOMPARE(test.d_pointer->m_colorWheel->hue(), color.h); 0104 } 0105 0106 void testSizeHints() 0107 { 0108 WheelColorPicker test{m_rgbColorSpace}; 0109 QVERIFY(test.minimumSizeHint().width() <= test.sizeHint().width()); 0110 QVERIFY(test.minimumSizeHint().height() <= test.sizeHint().height()); 0111 } 0112 0113 void testVerySmallWidgetSizes() 0114 { 0115 // Also very small widget sizes should not crash the widget. 0116 // This might happen because of divisions by 0, even when the widget 0117 // is bigger than 0 because of borders or offsets. We test this 0118 // here with various small sizes, always forcing in immediate 0119 // re-paint. 0120 WheelColorPicker myWidget{m_rgbColorSpace}; 0121 myWidget.show(); 0122 myWidget.resize(QSize()); 0123 myWidget.repaint(); 0124 myWidget.resize(QSize(-1, -1)); 0125 myWidget.repaint(); 0126 myWidget.resize(QSize(-1, 0)); 0127 myWidget.repaint(); 0128 myWidget.resize(QSize(0, -1)); 0129 myWidget.repaint(); 0130 myWidget.resize(QSize(0, 1)); 0131 myWidget.repaint(); 0132 myWidget.resize(QSize(1, 0)); 0133 myWidget.repaint(); 0134 myWidget.resize(QSize(1, 1)); 0135 myWidget.repaint(); 0136 myWidget.resize(QSize(2, 2)); 0137 myWidget.repaint(); 0138 myWidget.resize(QSize(3, 3)); 0139 myWidget.repaint(); 0140 myWidget.resize(QSize(4, 4)); 0141 myWidget.repaint(); 0142 myWidget.resize(QSize(5, 5)); 0143 myWidget.repaint(); 0144 myWidget.resize(QSize(6, 6)); 0145 myWidget.repaint(); 0146 myWidget.resize(QSize(7, 7)); 0147 myWidget.repaint(); 0148 myWidget.resize(QSize(8, 8)); 0149 myWidget.repaint(); 0150 myWidget.resize(QSize(9, 9)); 0151 myWidget.repaint(); 0152 myWidget.resize(QSize(10, 10)); 0153 myWidget.repaint(); 0154 myWidget.resize(QSize(11, 11)); 0155 myWidget.repaint(); 0156 myWidget.resize(QSize(12, 12)); 0157 myWidget.repaint(); 0158 myWidget.resize(QSize(13, 13)); 0159 myWidget.repaint(); 0160 myWidget.resize(QSize(14, 14)); 0161 myWidget.repaint(); 0162 } 0163 0164 void testSetOutOfGamutColors() 0165 { 0166 WheelColorPicker myWidget{m_rgbColorSpace}; 0167 myWidget.show(); 0168 myWidget.resize(QSize(400, 400)); 0169 0170 // Test that setting out-of-gamut colors works 0171 0172 const LchDouble myFirstColor{100, 150, 0}; 0173 myWidget.setCurrentColor(myFirstColor); 0174 QVERIFY(myFirstColor.hasSameCoordinates(myWidget.currentColor())); 0175 QVERIFY(myFirstColor.hasSameCoordinates( 0176 // This widget has no own storage of this property, but 0177 // relies on its child widget: 0178 myWidget.d_pointer->m_chromaLightnessDiagram->currentColor())); 0179 0180 const LchDouble mySecondColor{0, 150, 0}; 0181 myWidget.setCurrentColor(mySecondColor); 0182 QVERIFY(mySecondColor.hasSameCoordinates(myWidget.currentColor())); 0183 QVERIFY(mySecondColor.hasSameCoordinates( 0184 // This widget has no own storage of this property, but 0185 // relies on its child widget: 0186 myWidget.d_pointer->m_chromaLightnessDiagram->currentColor())); 0187 } 0188 0189 void testSetOutOfRangeColors() 0190 { 0191 WheelColorPicker myWidget{m_rgbColorSpace}; 0192 myWidget.show(); 0193 myWidget.resize(QSize(400, 400)); 0194 0195 // Test that setting colors, that are not only out-of-gamut colors 0196 // but also out of a reasonable range, works. 0197 0198 const LchDouble myFirstColor{300, 550, -10}; 0199 myWidget.setCurrentColor(myFirstColor); 0200 QVERIFY(myFirstColor.hasSameCoordinates(myWidget.currentColor())); 0201 QVERIFY(myFirstColor.hasSameCoordinates( 0202 // This widget has no own storage of this property, but 0203 // relies on its child widget: 0204 myWidget.d_pointer->m_chromaLightnessDiagram->currentColor())); 0205 0206 const LchDouble mySecondColor{-100, -150, 890}; 0207 myWidget.setCurrentColor(mySecondColor); 0208 QVERIFY(mySecondColor.hasSameCoordinates(myWidget.currentColor())); 0209 QVERIFY(mySecondColor.hasSameCoordinates( 0210 // This widget has no own storage of this property, but 0211 // relies on its child widget: 0212 myWidget.d_pointer->m_chromaLightnessDiagram->currentColor())); 0213 } 0214 0215 void testHueChanges() 0216 { 0217 WheelColorPicker myWidget{m_rgbColorSpace}; 0218 myWidget.resize(QSize(400, 400)); 0219 0220 // Choose a color with an extreme, but still clearly in-gamut chroma 0221 // (at least for the build-in sRGB gamut, with which we are testing): 0222 const LchDouble myColor{32, 115, 300}; 0223 myWidget.setCurrentColor(myColor); 0224 0225 // Move the wheel to a hue that allows much less chroma: 0226 myWidget.d_pointer->m_colorWheel->setHue(222); 0227 0228 // Now, the chroma-lightness coordinates are out-of-gamut for 0229 // the new hue. Test if they have been corrected: 0230 QVERIFY(m_rgbColorSpace->isCielchD50InGamut(myWidget.currentColor())); 0231 } 0232 }; 0233 0234 } // namespace PerceptualColor 0235 0236 QTEST_MAIN(PerceptualColor::TestWheelColorPicker) 0237 0238 // The following “include” is necessary because we do not use a header file: 0239 #include "testwheelcolorpicker.moc"