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_H
0005 #define RGBCOLORSPACE_H
0006 
0007 #include "constpropagatinguniquepointer.h"
0008 #include "genericcolor.h"
0009 #include "lchdouble.h"
0010 #include <lcms2.h>
0011 #include <qdatetime.h>
0012 #include <qglobal.h>
0013 #include <qmetatype.h>
0014 #include <qobject.h>
0015 #include <qrgb.h>
0016 #include <qsharedpointer.h>
0017 #include <qstring.h>
0018 #include <qversionnumber.h>
0019 class QRgba64;
0020 
0021 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0022 #include <qtmetamacros.h>
0023 #else
0024 #include <qobjectdefs.h>
0025 #endif
0026 
0027 Q_DECLARE_METATYPE(cmsColorSpaceSignature)
0028 Q_DECLARE_METATYPE(cmsProfileClassSignature)
0029 
0030 namespace PerceptualColor
0031 {
0032 class RgbColorSpacePrivate;
0033 
0034 /** @internal
0035  *
0036  * @brief Provides access to LittleCMS color management
0037  *
0038  * This class has no public constructor. Objects can be generated
0039  * with the static factory functions.
0040  *
0041  * @note The maximum accepted Cielch-D50/Cielab-D50 lightness range is
0042  * 0 to 100, and the maximum Cielch-D50 chroma is
0043  * @ref CielchD50Values::maximumChroma. Values outside of this
0044  * range are considered out-of-gamut, even if the profile
0045  * itself would accept them.
0046  *
0047  * @todo Unit tests for @ref RgbColorSpace, especially the to…() functions.
0048  *
0049  * @todo Unit tests for @ref profileMaximumCielchD50Chroma and
0050  *       @ref profileMaximumOklchChroma with all profiles that are available
0051  *       in the testbed.
0052  *
0053  * @todo Allow also other perceptual color spaces instead of CIELAB:
0054  *       This might be Oklab or Google’s HCT, CAM16 or DIN99. Attention:
0055  *       The range of valid values for Oklab is identical for L
0056  *       (0–1, or 0%–100%), but different for a and b:
0057  *       https://www.w3.org/TR/css-color-4/#ok-lab says up to 0.5, but
0058  *       we would have to actually test this. Therefore, also the
0059  *       @ref profileMaximumCielchD50Chroma would have to be provided for all
0060  *       these color spaces individually. Anyway, we could also
0061  *       output the data in a new data type for cylindrical coordinates
0062  *       (angle [in degree], radius, z), independent of the color
0063  *       space, which must always be cylindrical anyway as we have
0064  *       no support for anything else in our widgets.
0065  *
0066  *
0067  * @todo The sRGB colour space object should be implemented as a singleton.
0068  *       This is possible because it is thread-safe, and therefore it does
0069  *       not make sense to have more than one object of this class. At the
0070  *       same time, it is necessary that it implements the common interface
0071  *       of the colour space objects that are created on-the-fly from ICC
0072  *       profile files, therefore it cannot be static.
0073  *       As a consequence, translations within sRGB objects should always
0074  *       be dynamic instead of being done only once at instantiation time,
0075  *       because now the instantiation time is out of control of the library
0076  *       user. (And maybe even for ICC profiles we could provide ALL
0077  *       translations, be reading ALL possible translations at creating time
0078  *       and guarding them? Or would this be overkill?)
0079  *       The singleton pattern has special requirements
0080  *       for: 1) thread-safety. 2)  dynamic libraries. See Wikipedia
0081  *       for details!
0082  *
0083  * @todo Is it possible to split this into an interface and into
0084  *       various implementations (a slow but safe implementation for
0085  *       all valid ICC files, and a fast optimized implementation for sRGB
0086  *       only? If so, is it possible to get rid of the dependency from
0087  *       LittleCMS by implementing sRGB ourselves, and providing ICC support
0088  *       via an optional header-only header that would link against LittleCMS
0089  *       without injecting this dependency into our shared library? And
0090  *       this might be faster! The web page
0091  *       https://bottosson.github.io/misc/colorpicker/#91a7ee is only
0092  *       JavaScript and works faster than this library! See
0093  *       https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ and
0094  *       http://www.brucelindbloom.com/index.html?Math.html and
0095  *       http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
0096  *       for implementation details.
0097  *
0098  * @todo We return double precision values. But doesn’t use LittleCMS
0099  *       only 16-bit-integer internally? On the other hand: Using double
0100  *       precision allows to filter out out-of-range values…
0101  *
0102  * @todo Do not convert QRgba64 to RgbDouble, but use a transform that
0103  *       reads QRgba64 directly. While the benefit might not be big in
0104  *       this function, in general it would be good to review for which data
0105  *       types we provide transforms and minimize conversions.
0106  *
0107  * @todo In the API of this class, clarify the precision. If more than
0108  *       8 bit per channel, we have to switch from QRgb to QRgb64. But
0109  *       probably all OS APIs only accept 8 bit anyway? Is it worth the
0110  *       pain just because @ref ColorDialog can return <tt>QColor</tt>
0111  *       which provides 16 bit support?
0112  *
0113  * @todo Find more efficient ways of in-gamut detection. Maybe provide
0114  *       a subclass with optimized algorithms just for sRGB-build-in? */
0115 class RgbColorSpace : public QObject
0116 {
0117     Q_OBJECT
0118 
0119     /** @brief The absolute file path of the profile.
0120      *
0121      * @note This is empty for build-in profiles.
0122      *
0123      * @sa READ @ref profileAbsoluteFilePath() const */
0124     Q_PROPERTY(QString profileAbsoluteFilePath READ profileAbsoluteFilePath CONSTANT)
0125 
0126     /** @brief The class of the profile.
0127      *
0128      * This type is declared as type to Qt’s type system via
0129      * <tt>Q_DECLARE_METATYPE</tt>. Depending on your use case (for
0130      * example if you want to use for <em>queued</em> signal-slot connections),
0131      * you might consider calling <tt>qRegisterMetaType()</tt> for
0132      * this type, once you have a QApplication object.
0133      *
0134      * @sa READ @ref profileClass() const */
0135     Q_PROPERTY(cmsProfileClassSignature profileClass READ profileClass CONSTANT)
0136 
0137     /** @brief The color model of the color space which is described by
0138      * this profile.
0139      *
0140      * This type is declared as type to Qt’s type system via
0141      * <tt>Q_DECLARE_METATYPE</tt>. Depending on your use case (for
0142      * example if you want to use for <em>queued</em> signal-slot connections),
0143      * you might consider calling <tt>qRegisterMetaType()</tt> for
0144      * this type, once you have a QApplication object.
0145      *
0146      * @sa READ @ref profileColorModel() const */
0147     Q_PROPERTY(cmsColorSpaceSignature profileColorModel READ profileColorModel CONSTANT)
0148 
0149     /** @brief The copyright information of the profile.
0150      *
0151      * If supported by the underlying profile, this property is localized
0152      * to the current locale <em>at the moment of the constructor call</em>.
0153      *
0154      * @note This is empty if the information is not available.
0155      *
0156      * @sa READ @ref profileCopyright() const */
0157     Q_PROPERTY(QString profileCopyright READ profileCopyright CONSTANT)
0158 
0159     /** @brief The date and time of the creation of the profile.
0160      *
0161      * @note This is null if the information is not available.
0162      *
0163      * @sa READ @ref profileCreationDateTime() const */
0164     Q_PROPERTY(QDateTime profileCreationDateTime READ profileCreationDateTime CONSTANT)
0165 
0166     /** @brief The file size of the profile, measured in byte.
0167      *
0168      * @note This is <tt>-1</tt> for build-in profiles.
0169      *
0170      * @sa READ @ref profileFileSize() const */
0171     Q_PROPERTY(qint64 profileFileSize READ profileFileSize CONSTANT)
0172 
0173     /** @brief Wether or not the profile has a color lookup table (CLUT).
0174      *
0175      * @sa READ @ref profileHasClut() const */
0176     Q_PROPERTY(bool profileHasClut READ profileHasClut CONSTANT)
0177 
0178     /** @brief Wether or not the profile has a matrix shaper.
0179      *
0180      * @sa READ @ref profileHasMatrixShaper() const */
0181     Q_PROPERTY(bool profileHasMatrixShaper READ profileHasMatrixShaper CONSTANT)
0182 
0183     /** @brief The ICC version of the profile.
0184      *
0185      * @sa READ @ref profileIccVersion() const */
0186     Q_PROPERTY(QVersionNumber profileIccVersion READ profileIccVersion CONSTANT)
0187 
0188     /** @brief The manufacturer information of the profile.
0189      *
0190      * If supported by the underlying profile, this property is localized
0191      * to the current locale <em>at the moment of the constructor call</em>.
0192      *
0193      * @note This is empty if the information is not available.
0194      *
0195      * @sa READ @ref profileManufacturer() const */
0196     Q_PROPERTY(QString profileManufacturer READ profileManufacturer CONSTANT)
0197 
0198     /** @brief The maximum CIELch-D50 chroma of the profile.
0199      *
0200      * This value is equal or slightly bigger than the actual maximum chroma.
0201      *
0202      * @note This is the result of an auto-detection, which might theoretically
0203      * in very rare cases return a value that is smaller than the actual
0204      * maximum chroma.
0205      *
0206      * @sa READ @ref profileMaximumCielchD50Chroma() const */
0207     Q_PROPERTY(double profileMaximumCielchD50Chroma READ profileMaximumCielchD50Chroma CONSTANT)
0208 
0209     /** @brief The maximum Oklch chroma of the profile.
0210      *
0211      * This value is equal or slightly bigger than the actual maximum chroma.
0212      *
0213      * @note This is the result of an auto-detection, which might theoretically
0214      * in very rare cases return a value that is smaller than the actual
0215      * maximum chroma.
0216      *
0217      * @sa READ @ref profileMaximumOklchChroma() const */
0218     Q_PROPERTY(double profileMaximumOklchChroma READ profileMaximumOklchChroma CONSTANT)
0219 
0220     /** @brief The model information of the profile.
0221      *
0222      * If supported by the underlying profile, this property is localized
0223      * to the current locale <em>at the moment of the constructor call</em>.
0224      *
0225      * @note This is empty if the information is not available.
0226      *
0227      * @sa READ @ref profileModel() const */
0228     Q_PROPERTY(QString profileModel READ profileModel CONSTANT)
0229 
0230     /** @brief The name of the profile.
0231      *
0232      * If supported by the underlying profile, this property is localized
0233      * to the current locale <em>at the moment of the constructor call</em>.
0234      *
0235      * Note that this string might be very long in some profiles. On some
0236      * UI elements, maybe it should be elided (truncate it and put “…” at
0237      * the end).
0238      *
0239      * @note This is empty if the information is not available.
0240      *
0241      * @sa READ @ref profileName() const */
0242     Q_PROPERTY(QString profileName READ profileName CONSTANT)
0243 
0244     /** @brief The name of the profile.
0245      *
0246      * If supported by the underlying profile, this property is localized
0247      * to the current locale <em>at the moment of the constructor call</em>.
0248      *
0249      * This type is declared as type to Qt’s type system via
0250      * <tt>Q_DECLARE_METATYPE</tt>. Depending on your use case (for
0251      * example if you want to use for <em>queued</em> signal-slot connections),
0252      * you might consider calling <tt>qRegisterMetaType()</tt> for
0253      * this type, once you have a QApplication object.
0254      *
0255      * @sa READ @ref profileName() const */
0256     Q_PROPERTY(cmsColorSpaceSignature profilePcsColorModel READ profilePcsColorModel CONSTANT)
0257 
0258 public: // Static factory functions
0259     [[nodiscard]] Q_INVOKABLE static QSharedPointer<PerceptualColor::RgbColorSpace> createFromFile(const QString &fileName);
0260     [[nodiscard]] Q_INVOKABLE static QSharedPointer<PerceptualColor::RgbColorSpace> createSrgb();
0261 
0262 public:
0263     virtual ~RgbColorSpace() noexcept override;
0264     [[nodiscard]] Q_INVOKABLE virtual bool isCielabD50InGamut(const cmsCIELab &lab) const;
0265     [[nodiscard]] Q_INVOKABLE virtual bool isCielchD50InGamut(const PerceptualColor::LchDouble &lch) const;
0266     [[nodiscard]] Q_INVOKABLE virtual bool isOklchInGamut(const PerceptualColor::LchDouble &lch) const;
0267     /** @brief Getter for property @ref profileAbsoluteFilePath
0268      *  @returns the property @ref profileAbsoluteFilePath */
0269     [[nodiscard]] QString profileAbsoluteFilePath() const;
0270     /** @brief Getter for property @ref profileClass
0271      *  @returns the property @ref profileClass */
0272     [[nodiscard]] cmsProfileClassSignature profileClass() const;
0273     /** @brief Getter for property @ref profileColorModel
0274      *  @returns the property @ref profileColorModel */
0275     [[nodiscard]] cmsColorSpaceSignature profileColorModel() const;
0276     /** @brief Getter for property @ref profileCopyright
0277      *  @returns the property @ref profileCopyright */
0278     [[nodiscard]] QString profileCopyright() const;
0279     /** @brief Getter for property @ref profileCreationDateTime
0280      *  @returns the property @ref profileCreationDateTime */
0281     [[nodiscard]] QDateTime profileCreationDateTime() const;
0282     /** @brief Getter for property @ref profileFileSize
0283      *  @returns the property @ref profileFileSize */
0284     [[nodiscard]] qint64 profileFileSize() const;
0285     /** @brief Getter for property @ref profileHasClut
0286      *  @returns the property @ref profileHasClut */
0287     [[nodiscard]] bool profileHasClut() const;
0288     /** @brief Getter for property @ref profileHasMatrixShaper
0289      *  @returns the property @ref profileHasMatrixShaper */
0290     [[nodiscard]] bool profileHasMatrixShaper() const;
0291     /** @brief Getter for property @ref profileIccVersion
0292      *  @returns the property @ref profileIccVersion */
0293     [[nodiscard]] QVersionNumber profileIccVersion() const;
0294     /** @brief Getter for property @ref profileManufacturer
0295      *  @returns the property @ref profileManufacturer */
0296     [[nodiscard]] QString profileManufacturer() const;
0297     /** @brief Getter for property @ref profileMaximumCielchD50Chroma
0298      *  @returns the property @ref profileMaximumCielchD50Chroma */
0299     [[nodiscard]] double profileMaximumCielchD50Chroma() const;
0300     /** @brief Getter for property @ref profileMaximumOklchChroma
0301      *  @returns the property @ref profileMaximumOklchChroma */
0302     [[nodiscard]] double profileMaximumOklchChroma() const;
0303     /** @brief Getter for property @ref profileModel
0304      *  @returns the property @ref profileModel */
0305     [[nodiscard]] QString profileModel() const;
0306     /** @brief Getter for property @ref profileName
0307      *  @returns the property @ref profileName */
0308     [[nodiscard]] QString profileName() const;
0309     /** @brief Getter for property @ref profilePcsColorModel
0310      *  @returns the property @ref profilePcsColorModel */
0311     [[nodiscard]] cmsColorSpaceSignature profilePcsColorModel() const;
0312     [[nodiscard]] Q_INVOKABLE virtual PerceptualColor::LchDouble reduceCielchD50ChromaToFitIntoGamut(const PerceptualColor::LchDouble &cielchD50color) const;
0313     [[nodiscard]] Q_INVOKABLE virtual PerceptualColor::LchDouble reduceOklchChromaToFitIntoGamut(const PerceptualColor::LchDouble &oklchColor) const;
0314     [[nodiscard]] Q_INVOKABLE virtual cmsCIELab toCielabD50(const QRgba64 rgbColor) const;
0315     [[nodiscard]] Q_INVOKABLE virtual PerceptualColor::LchDouble toCielchD50Double(const QRgba64 rgbColor) const;
0316     [[nodiscard]] Q_INVOKABLE virtual QRgb fromCielchD50ToQRgbBound(const PerceptualColor::LchDouble &lch) const;
0317     [[nodiscard]] Q_INVOKABLE virtual QRgb fromCielabD50ToQRgbOrTransparent(const cmsCIELab &lab) const;
0318     [[nodiscard]] Q_INVOKABLE virtual PerceptualColor::GenericColor fromCielchD50ToRgb1(const PerceptualColor::LchDouble &lch) const;
0319 
0320 private:
0321     Q_DISABLE_COPY(RgbColorSpace)
0322 
0323     /** @internal
0324      *
0325      * @brief Private constructor.
0326      *
0327      * @param parent The widget’s parent widget. This parameter will be
0328      * passed to the base class’s constructor. */
0329     explicit RgbColorSpace(QObject *parent = nullptr);
0330 
0331     /** @internal
0332      *
0333      * @brief Declare the private implementation as friend class.
0334      *
0335      * This allows the private class to access the protected members and
0336      * functions of instances of <em>this</em> class. */
0337     friend class RgbColorSpacePrivate;
0338     /** @brief Pointer to implementation (pimpl) */
0339     ConstPropagatingUniquePointer<RgbColorSpacePrivate> d_pointer;
0340 
0341     /** @internal @brief Only for unit tests. */
0342     friend class TestRgbColorSpace;
0343 };
0344 
0345 } // namespace PerceptualColor
0346 
0347 #endif // RGBCOLORSPACE_H