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