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

0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT
0003 
0004 #ifndef RGBCOLORSPACE_P_H
0005 #define RGBCOLORSPACE_P_H
0006 
0007 // Include the header of the public class of this private implementation.
0008 // #include "rgbcolorspace.h"
0009 
0010 #include "cielchd50values.h"
0011 #include "constpropagatingrawpointer.h"
0012 #include "helperconstants.h"
0013 #include "oklchvalues.h"
0014 #include <lcms2.h>
0015 #include <qdatetime.h>
0016 #include <qglobal.h>
0017 #include <qmap.h>
0018 #include <qstring.h>
0019 #include <qversionnumber.h>
0020 
0021 namespace PerceptualColor
0022 {
0023 class RgbColorSpace;
0024 
0025 /** @internal
0026  *
0027  *  @brief Private implementation within the <em>Pointer to
0028  *  implementation</em> idiom */
0029 class RgbColorSpacePrivate final
0030 {
0031 private:
0032     [[nodiscard]] static QMap<cmsUInt32Number, QString> getIntentList();
0033 
0034 public:
0035     explicit RgbColorSpacePrivate(RgbColorSpace *backLink);
0036     /** @brief Default destructor
0037      *
0038      * The destructor is non-<tt>virtual</tt> because
0039      * the class as a whole is <tt>final</tt>. */
0040     ~RgbColorSpacePrivate() noexcept = default;
0041 
0042     // Data members:
0043     /** @brief The darkest in-gamut point on the L* axis.
0044      * @sa whitepointL
0045      *
0046      * @internal
0047      *
0048      * @todo Use cmsDetectBlackPoint? But “our” “blackpoint” is always on
0049      * the grey axis, but the real blackpoint not? Document this? */
0050     qreal m_cielabD50BlackpointL = 0;
0051     /** @brief The lightest in-gamut point on the L* axis.
0052      * @sa blackpointL() */
0053     qreal m_cielabD50WhitepointL = 100;
0054     /** @brief The darkest in-gamut point on the L* axis.
0055      * @sa whitepointL
0056      *
0057      * @internal
0058      *
0059      * @todo Use cmsDetectBlackPoint? But “our” “blackpoint” is always on
0060      * the grey axis, but the real blackpoint not? Document this? */
0061     qreal m_oklabBlackpointL = 0;
0062     /** @brief The lightest in-gamut point on the L* axis.
0063      * @sa blackpointL() */
0064     qreal m_oklabWhitepointL = 1;
0065     /** @brief Internal storage for property
0066      * @ref RgbColorSpace::profileAbsoluteFilePath */
0067     QString m_profileAbsoluteFilePath;
0068     /** @brief Internal storage for property
0069      * @ref RgbColorSpace::profileClass */
0070     cmsProfileClassSignature m_profileClass;
0071     /** @brief Internal storage for property
0072      * @ref RgbColorSpace::profileColorModel */
0073     cmsColorSpaceSignature m_profileColorModel;
0074     /** @brief Internal storage for property
0075      * @ref RgbColorSpace::profileCopyright */
0076     QString m_profileCopyright;
0077     /** @brief Internal storage for property
0078      * @ref RgbColorSpace::profileCreationDateTime */
0079     QDateTime m_profileCreationDateTime;
0080     /** @brief Internal storage for property
0081      * @ref RgbColorSpace::profileFileSize */
0082     qint64 m_profileFileSize = -1;
0083     /** @brief Internal storage for property
0084      * @ref RgbColorSpace::profileHasClut */
0085     bool m_profileHasClut = false;
0086     /** @brief Internal storage for property
0087      * @ref RgbColorSpace::profileHasMatrixShaper */
0088     bool m_profileHasMatrixShaper = false;
0089     /** @brief Internal storage for property
0090      * @ref RgbColorSpace::profileIccVersion */
0091     QVersionNumber m_profileIccVersion;
0092     /** @brief Internal storage for property
0093      * @ref RgbColorSpace::profileManufacturer */
0094     QString m_profileManufacturer;
0095     /** @brief Internal storage for property
0096      * @ref RgbColorSpace::profileMaximumCielchD50Chroma */
0097     double m_profileMaximumCielchD50Chroma = CielchD50Values::maximumChroma;
0098     /** @brief Internal storage for property
0099      * @ref RgbColorSpace::profileMaximumOklchChroma */
0100     double m_profileMaximumOklchChroma = OklchValues::maximumChroma;
0101     /** @brief Internal storage for property
0102      * @ref RgbColorSpace::profileModel */
0103     QString m_profileModel;
0104     /** @brief Internal storage for property
0105      * @ref RgbColorSpace::profileName */
0106     QString m_profileName;
0107     /** @brief Internal storage for property
0108      * @ref RgbColorSpace::profilePcsColorModel */
0109     cmsColorSpaceSignature m_profilePcsColorModel;
0110     /** @brief A handle to a LittleCMS transform. */
0111     cmsHTRANSFORM m_transformCielabD50ToRgb16Handle = nullptr;
0112     /** @brief A handle to a LittleCMS transform. */
0113     cmsHTRANSFORM m_transformCielabD50ToRgbHandle = nullptr;
0114     /** @brief A handle to a LittleCMS transform. */
0115     cmsHTRANSFORM m_transformRgbToCielabD50Handle = nullptr;
0116 
0117     // Functions:
0118     static void deleteTransform(cmsHTRANSFORM *transformHandle);
0119     [[nodiscard]] double detectMaximumCielchD50Chroma() const;
0120     [[nodiscard]] double detectMaximumOklchChroma() const;
0121     [[nodiscard]] static QDateTime getCreationDateTimeFromProfile(cmsHPROFILE profileHandle);
0122     [[nodiscard]] static QVersionNumber getIccVersionFromProfile(cmsHPROFILE profileHandle);
0123     [[nodiscard]] static QString getInformationFromProfile(cmsHPROFILE profileHandle, cmsInfoType infoType);
0124     [[nodiscard]] bool initialize(cmsHPROFILE rgbProfileHandle);
0125 
0126     /** @brief The rendering intents supported by the LittleCMS library.
0127      *
0128      * Contains all rendering intents supported by the LittleCMS library
0129      * against which this we are currently linking. Each entry contains
0130      * the code and the (english-language) description just as provided
0131      * by LittleCMS.
0132      *
0133      * Note that LittleCMS supports as built-in intents the four official
0134      * ICC intents and also some other, non-ICC intents. Furthermore,
0135      * LittleCMS plugins can provide even more intents. As of LittleCMS 2.13
0136      * the following built-in intents are available:
0137      *
0138      * | Type    | Macro name                                    | Code |
0139      * | :------ | :-------------------------------------------- | ---: |
0140      * | ICC     | INTENT_PERCEPTUAL                             |    0 |
0141      * | ICC     | INTENT_RELATIVE_COLORIMETRIC                  |    1 |
0142      * | ICC     | INTENT_SATURATION                             |    2 |
0143      * | ICC     | INTENT_ABSOLUTE_COLORIMETRIC                  |    3 |
0144      * | Non-ICC | INTENT_PRESERVE_K_ONLY_PERCEPTUAL             |   10 |
0145      * | Non-ICC | INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC  |   11 |
0146      * | Non-ICC | INTENT_PRESERVE_K_ONLY_SATURATION             |   12 |
0147      * | Non-ICC | INTENT_PRESERVE_K_PLANE_PERCEPTUAL            |   13 |
0148      * | Non-ICC | INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC |   14 |
0149      * | Non-ICC | INTENT_PRESERVE_K_PLANE_SATURATION            |   15 |
0150      *
0151      * @todo Either actually <em>use</em> this code or <em>remove</em> this
0152      * code. */
0153     static inline const QMap<cmsUInt32Number, QString> intentList = getIntentList();
0154 
0155     /** @brief Precision of HSV hue during maximum-chroma detection.
0156      *
0157      * @todo A value smaller than 0.001 does not make sense
0158      * currently, because QColor has only a limited precision for
0159      * HSV conversions. Furthermore, since Qt6 it’s floating point interface
0160      * has been defined with “float”. For a more exact solution, we would
0161      * have to implement our own HSV conversion first. */
0162     static constexpr double chromaDetectionHuePrecision = gamutPrecisionCielab;
0163     /** @brief Increment factor for the maximum-chroma detection.
0164      *
0165      * The maximum-chroma detection, regardless of the precision,
0166      * might always return a value that is a bit too small. However,
0167      * we want to have @ref RgbColorSpace::profileMaximumCielchD50Chroma and
0168      * @ref RgbColorSpace::profileMaximumOklchChroma values that are equal
0169      * or slightly bigger than the actual maximum-chroma, to make sure to
0170      * not exclude valid values. Therefore, @ref detectMaximumCielchD50Chroma()
0171      * and @ref detectMaximumOklchChroma use this increment factor to
0172      * slightly increment the outcome of the chroma detection relative to
0173      * the original value, as a safety margin. Note that additionally,
0174      * an absolute increment should also be added, because of limited
0175      * precision in floating point operations. */
0176     static constexpr double chromaDetectionIncrementFactor = 1.02;
0177     /** @brief For detecting CIELab in-gamut or out-of-gamut colors.
0178      *
0179      * For gamut detection, a roundtrip conversion is performed: Lab values
0180      * are converted to an RGB color space and backwards. If the distance
0181      * in euclidean space between the the original Lab value and the result
0182      * of the roundtrip is smaller than a certain value, it is considered
0183      * as an in-gamut value.
0184      *
0185      * This deviation limit should be as small as possible for a more correct
0186      * gamut boundary. But it must unfortunately also be big enough to ignore
0187      * rounding errors. The current value was chosen by trial-and-error. */
0188     static constexpr qreal cielabDeviationLimit = 0.5;
0189     /** @brief For detecting Oklab in-gamut or out-of-gamut colors.
0190      *
0191      * For gamut detection, a roundtrip conversion is performed: Lab values
0192      * are converted to an RGB color space and backwards. If the distance
0193      * in euclidean space between the the original Lab value and the result
0194      * of the roundtrip is smaller than a certain value, it is considered
0195      * as an in-gamut value.
0196      *
0197      * This deviation limit should be as small as possible for a more correct
0198      * gamut boundary. But it must unfortunately also be big enough to ignore
0199      * rounding errors. The current value was chosen by trial-and-error. */
0200     static constexpr qreal oklabDeviationLimit = 0.001;
0201 
0202 private:
0203     Q_DISABLE_COPY(RgbColorSpacePrivate)
0204 
0205     /** @brief Pointer to the object from which <em>this</em> object
0206      *  is the private implementation. */
0207     ConstPropagatingRawPointer<RgbColorSpace> q_pointer;
0208 };
0209 
0210 } // namespace PerceptualColor
0211 
0212 #endif // RGBCOLORSPACE_P_H