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

0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT
0003 
0004 #ifndef CHROMALIGHTNESSDIAGRAM_P_H
0005 #define CHROMALIGHTNESSDIAGRAM_P_H
0006 
0007 // Include the header of the public class of this private implementation.
0008 // #include "chromalightnessdiagram.h"
0009 
0010 #include "asyncimageprovider.h"
0011 #include "chromalightnessimageparameters.h"
0012 #include "constpropagatingrawpointer.h"
0013 #include "lchdouble.h"
0014 #include <cmath>
0015 #include <functional>
0016 #include <limits>
0017 #include <optional>
0018 #include <qglobal.h>
0019 #include <qsharedpointer.h>
0020 #include <qsize.h>
0021 class QRect;
0022 class QPoint;
0023 
0024 namespace PerceptualColor
0025 {
0026 class ChromaLightnessDiagram;
0027 class RgbColorSpace;
0028 
0029 /** @internal
0030  *
0031  *  @brief Private implementation within the <em>Pointer to
0032  *  implementation</em> idiom */
0033 class ChromaLightnessDiagramPrivate final
0034 {
0035 public:
0036     explicit ChromaLightnessDiagramPrivate(ChromaLightnessDiagram *backLink);
0037     /** @brief Default destructor
0038      *
0039      * The destructor is non-<tt>virtual</tt> because
0040      * the class as a whole is <tt>final</tt>. */
0041     ~ChromaLightnessDiagramPrivate() noexcept = default;
0042 
0043     // Member variables
0044     /** @brief The image of the chroma-lightness diagram itself. */
0045     AsyncImageProvider<ChromaLightnessImageParameters> m_chromaLightnessImage;
0046     /** @brief Properties for @ref m_chromaLightnessImage. */
0047     ChromaLightnessImageParameters m_chromaLightnessImageParameters;
0048     /** @brief Internal storage of
0049      * the @ref ChromaLightnessDiagram::currentColor property */
0050     LchDouble m_currentColor;
0051     /** @brief Holds if currently a mouse event is active or not.
0052      *
0053      * Default value is <tt>false</tt>.
0054      * - A mouse event gets typically activated on a
0055      *   @ref ChromaLightnessDiagram::mousePressEvent()
0056      *   done within the gamut diagram. The value is set to <tt>true</tt>.
0057      * - While active, all @ref ChromaLightnessDiagram::mouseMoveEvent() will
0058      *   move the diagram’s color handle.
0059      * - Once a @ref ChromaLightnessDiagram::mouseReleaseEvent() occurs, the
0060      *   value is set to <tt>false</tt>. Further mouse movements will not
0061      *   move the handle anymore.
0062      *
0063      * This is done because Qt’s default mouse tracking reacts on all clicks
0064      * within the (rectangular) widget. However, <em>this</em> widget is meant
0065      * as a circular widget, only reacting on mouse events within the circle;
0066      * this requires this custom implementation. */
0067     bool m_isMouseEventActive = false; // TODO Remove me!
0068     /** @brief Pointer to RgbColorSpace() object */
0069     QSharedPointer<RgbColorSpace> m_rgbColorSpace;
0070 
0071     // Member functions
0072     [[nodiscard]] QSize calculateImageSizePhysical() const;
0073     [[nodiscard]] int defaultBorderPhysical() const;
0074     /** @internal
0075      *
0076      * @brief Calculate how far a value is from a given range.
0077      * @pre <tt>low</tt> ≤ <tt>high</tt>
0078      * @param low the lower limit
0079      * @param x the value that will be tested
0080      * @param high the higher limit
0081      * @returns <tt>0</tt> if the value is within the range. The distance
0082      * to the nearest border of the range otherwise. */
0083     template<typename T>
0084     [[nodiscard]] static constexpr T distanceFromRange(const T &low, const T &x, const T &high)
0085     {
0086         if (x < low) {
0087             return low - x;
0088         }
0089         if (x > high) {
0090             return x - high;
0091         }
0092         if constexpr ( //
0093             std::numeric_limits<T>::has_quiet_NaN //
0094             || std::numeric_limits<T>::has_signaling_NaN //
0095         ) {
0096             if (std::isnan(low) || std::isnan(x) || std::isnan(high)) {
0097                 return std::numeric_limits<T>::quiet_NaN();
0098             }
0099         }
0100         return 0;
0101     }
0102     [[nodiscard]] LchDouble fromWidgetPixelPositionToColor(const QPoint widgetPixelPosition) const;
0103     [[nodiscard]] bool isWidgetPixelPositionInGamut(const QPoint widgetPixelPosition) const;
0104     [[nodiscard]] int leftBorderPhysical() const;
0105     [[nodiscard]] PerceptualColor::LchDouble nearestInGamutColorByAdjustingChromaLightness(const double chroma, const double lightness);
0106     [[nodiscard]] std::optional<QPoint> nearestInGamutPixelPosition(const QPoint originalPixelPosition);
0107     [[nodiscard]] static std::optional<QPoint>
0108     nearestNeighborSearch(const QPoint point, const QRect searchRectangle, const std::function<bool(const QPoint)> &doesPointExist);
0109     void setCurrentColorFromWidgetPixelPosition(const QPoint widgetPixelPosition);
0110 
0111 private:
0112     Q_DISABLE_COPY(ChromaLightnessDiagramPrivate)
0113 
0114     /** @brief Pointer to the object from which <em>this</em> object
0115      *  is the private implementation. */
0116     ConstPropagatingRawPointer<ChromaLightnessDiagram> q_pointer;
0117 };
0118 
0119 } // namespace PerceptualColor
0120 
0121 #endif // CHROMALIGHTNESSDIAGRAM_P_H