File indexing completed on 2024-06-23 11:14:18

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"