File indexing completed on 2024-05-12 04:44:29

0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT
0003 
0004 #ifndef CHROMAHUEDIAGRAM_H
0005 #define CHROMAHUEDIAGRAM_H
0006 
0007 #include "abstractdiagram.h"
0008 #include "constpropagatinguniquepointer.h"
0009 #include "importexport.h"
0010 #include "lchdouble.h"
0011 #include <qglobal.h>
0012 #include <qsharedpointer.h>
0013 #include <qsize.h>
0014 
0015 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0016 #include <qtmetamacros.h>
0017 #else
0018 #include <qobjectdefs.h>
0019 #include <qstring.h>
0020 class QObject;
0021 #endif
0022 
0023 class QKeyEvent;
0024 class QMouseEvent;
0025 class QPaintEvent;
0026 class QResizeEvent;
0027 class QWheelEvent;
0028 class QWidget;
0029 
0030 namespace PerceptualColor
0031 {
0032 class ChromaHueDiagramPrivate;
0033 }
0034 namespace PerceptualColor
0035 {
0036 class RgbColorSpace;
0037 }
0038 
0039 namespace PerceptualColor
0040 {
0041 /** @brief A widget for selecting chroma and hue in LCH color space
0042  *
0043  * This widget displays the plan of chroma and hue
0044  * (that means a diagram of the radius and the angle of the
0045  * LCH color space respectively the a axis and the b axis of the
0046  * <a href="https://en.wikipedia.org/wiki/CIELAB_color_space">
0047  * Lab color model</a>) at a given lightness.
0048  *
0049  * @image html ChromaHueDiagram.png "ChromaHueDiagram" width=250
0050  *
0051  * The widget allows the user to select a color (chroma and hue) within the
0052  * specified gamut at a given lightness. It reacts on mouse events and on
0053  * keyboard events (see @ref keyPressEvent() for details).
0054  *
0055  * The form of the selection handle (that always indicates the distance from
0056  * the center of the diagram) and the circular form of the widget, all this
0057  * helps the user to understand intuitively that he is moving within a
0058  * polar coordinate system and to capture easily the current radius
0059  * and angle.
0060  *
0061  * Usage example: @snippet testchromahuediagram.cpp instantiate
0062  *
0063  * @note This widget <em>always</em> accepts focus by a mouse click within
0064  * the circle. This happens regardless of the <tt>QWidget::focusPolicy</tt>
0065  * property:
0066  * - If you set the <tt>QWidget::focusPolicy</tt> property to a
0067  *   value that does not accept focus by mouse click, the focus
0068  *   will nevertheless be accepted for clicks within the actual circle.
0069  *   (This is the default behavior.)
0070  * - If you set the <tt>QWidget::focusPolicy</tt> property to a
0071  *   value that accepts focus by mouse click, the focus will not only be
0072  *   accepted for clicks within the actual circle, but also for clicks
0073  *   anywhere within the (rectangular) widget.
0074  *
0075  * @internal
0076  *
0077  * @todo BUG Left-click in the gray area inside the wheel but outside
0078  * the displayed gamut; maintain the click button and do not move the
0079  * mouse. Actual behavior: Mouse cursor is invisible. Expected behaviour:
0080  * Mouse cursor stays visible (as it would be anyway after moving the mouse).
0081  *
0082  * @todo BUG Click on the wheel. Actual behaviour: Nothing. Expected behavior:
0083  * The selected color follows the cursor.
0084  *
0085  * @todo BUG Wide gamut RGB: RGB 51 255 51. Chroma-Hue-Diagram: The handle
0086  * is drawn outside the circle. This should never happen! See also
0087  * @ref PerceptualColor::CielchD50Values::maximumChroma
0088  *
0089  * @todo The hue circle around chroma-hue diagram might be confusing because
0090  * it is colored, but it is not a usable slider like all other colored
0091  * elements. We could remove it. But on the other hand, it is also useful
0092  * to have it. Maybe make it look different than for @ref WheelColorPicker,
0093  * for instance make it thinner and make it touch the gray diagram area?
0094  * Maybe make it react on mouse events just like the inner part of the diagram.
0095  *
0096  * @todo Add a circular indicator to the handle, indicating the values
0097  * with identical chroma? Only during mouse dragging? Or always? Or never?
0098  *
0099  * @todo Example code: How to create the widget at a given
0100  * lightness.
0101  *
0102  * @todo Allow to touch the widget on the color wheel (and have a reaction).
0103  *
0104  * @todo Use a cross cursor for better usability: The cross cursor indicates
0105  * to the user that an area can be clicked in. Do it only within the gamut
0106  * (where the color handle can actually go) or in the hole gray circle,
0107  * which is the mouse sensitive area (but out of the gamut the color
0108  * handle cannot follow)?
0109  *
0110  * @todo Support additional mouse buttons. For example, “forward” and
0111  * “backward” could be used to increase or decrease the radius.
0112  *
0113  * @todo What if black or white are out of gamut on L=0.1 or L=99.9? Where
0114  * are the handles placed? Visible or invisible? How to react? Should
0115  * there be always a physical pixel in the middle that is visible (black
0116  * or white) even when out of gamut?
0117  *
0118  * @todo Optimization: It might be possible to <em>not</em> store
0119  * both, @ref ChromaHueDiagramPrivate::m_chromaHueImage and
0120  * a @ref ChromaHueDiagramPrivate::m_wheelImage. Instead, both could
0121  * be combined into one single image. As long as there is a (big enough)
0122  * safety margin between the color wheel and the inner (circular) diagram
0123  * surface, it should be possible to erase the original data and paint
0124  * new data above without rendering artefacts for each of these two elements
0125  * of the image. */
0126 class PERCEPTUALCOLOR_IMPORTEXPORT ChromaHueDiagram : public AbstractDiagram
0127 {
0128     Q_OBJECT
0129 
0130     /** @brief Currently selected color
0131      *
0132      * The widget allows the user to change the LCH chroma and the LCH hue
0133      * values. However, the LCH lightness value cannot be changed by the
0134      * user, but only by the programmer through this property.
0135      *
0136      * The programmer can set this property to out-of-gamut values; the
0137      * user cannot.
0138      *
0139      * @sa READ @ref currentColor() const
0140      * @sa WRITE @ref setCurrentColor()
0141      * @sa NOTIFY @ref currentColorChanged() */
0142     Q_PROPERTY(LchDouble currentColor READ currentColor WRITE setCurrentColor NOTIFY currentColorChanged)
0143 
0144 public:
0145     Q_INVOKABLE explicit ChromaHueDiagram(const QSharedPointer<PerceptualColor::RgbColorSpace> &colorSpace, QWidget *parent = nullptr);
0146     virtual ~ChromaHueDiagram() noexcept override;
0147     /** @brief Getter for property @ref currentColor
0148      *  @returns the property @ref currentColor */
0149     [[nodiscard]] LchDouble currentColor() const;
0150     [[nodiscard]] virtual QSize minimumSizeHint() const override;
0151     [[nodiscard]] virtual QSize sizeHint() const override;
0152 
0153 public Q_SLOTS:
0154     void setCurrentColor(const PerceptualColor::LchDouble &newCurrentColor);
0155 
0156 Q_SIGNALS:
0157     /** @brief Notify signal for property @ref currentColor.
0158      *  @param newCurrentColor the new current color */
0159     void currentColorChanged(const PerceptualColor::LchDouble &newCurrentColor);
0160 
0161 protected:
0162     virtual void keyPressEvent(QKeyEvent *event) override;
0163     virtual void mouseMoveEvent(QMouseEvent *event) override;
0164     virtual void mousePressEvent(QMouseEvent *event) override;
0165     virtual void mouseReleaseEvent(QMouseEvent *event) override;
0166     virtual void paintEvent(QPaintEvent *event) override;
0167     virtual void resizeEvent(QResizeEvent *event) override;
0168     virtual void wheelEvent(QWheelEvent *event) override;
0169 
0170 private:
0171     Q_DISABLE_COPY(ChromaHueDiagram)
0172 
0173     /** @internal
0174      *
0175      * @brief Declare the private implementation as friend class.
0176      *
0177      * This allows the private class to access the protected members and
0178      * functions of instances of <em>this</em> class. */
0179     friend class ChromaHueDiagramPrivate;
0180     /** @brief Pointer to implementation (pimpl) */
0181     ConstPropagatingUniquePointer<ChromaHueDiagramPrivate> d_pointer;
0182 
0183     /** @internal @brief Only for unit tests. */
0184     friend class TestChromaHueDiagram;
0185 };
0186 
0187 } // namespace PerceptualColor
0188 
0189 #endif // CHROMAHUEDIAGRAM_H