Perceptual Color

rgbcolorspace.h
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4#ifndef RGBCOLORSPACE_H
5#define RGBCOLORSPACE_H
6
7#include "constpropagatinguniquepointer.h"
8#include "genericcolor.h"
9#include "lchdouble.h"
10#include <lcms2.h>
11#include <qdatetime.h>
12#include <qglobal.h>
13#include <qmetatype.h>
14#include <qobject.h>
15#include <qrgb.h>
16#include <qsharedpointer.h>
17#include <qstring.h>
18#include <qversionnumber.h>
19class QRgba64;
20
21#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
22#include <qtmetamacros.h>
23#else
24#include <qobjectdefs.h>
25#endif
26
27Q_DECLARE_METATYPE(cmsColorSpaceSignature)
28Q_DECLARE_METATYPE(cmsProfileClassSignature)
29
30namespace PerceptualColor
31{
32class RgbColorSpacePrivate;
33
34/** @internal
35 *
36 * @brief Provides access to LittleCMS color management
37 *
38 * This class has no public constructor. Objects can be generated
39 * with the static factory functions.
40 *
41 * @note The maximum accepted Cielch-D50/Cielab-D50 lightness range is
42 * 0 to 100, and the maximum Cielch-D50 chroma is
43 * @ref CielchD50Values::maximumChroma. Values outside of this
44 * range are considered out-of-gamut, even if the profile
45 * itself would accept them.
46 *
47 * @todo Unit tests for @ref RgbColorSpace, especially the to…() functions.
48 *
49 * @todo Unit tests for @ref profileMaximumCielchD50Chroma and
50 * @ref profileMaximumOklchChroma with all profiles that are available
51 * in the testbed.
52 *
53 * @todo Allow also other perceptual color spaces instead of CIELAB:
54 * This might be Oklab or Google’s HCT, CAM16 or DIN99. Attention:
55 * The range of valid values for Oklab is identical for L
56 * (0–1, or 0%–100%), but different for a and b:
57 * https://www.w3.org/TR/css-color-4/#ok-lab says up to 0.5, but
58 * we would have to actually test this. Therefore, also the
59 * @ref profileMaximumCielchD50Chroma would have to be provided for all
60 * these color spaces individually. Anyway, we could also
61 * output the data in a new data type for cylindrical coordinates
62 * (angle [in degree], radius, z), independent of the color
63 * space, which must always be cylindrical anyway as we have
64 * no support for anything else in our widgets.
65 *
66 *
67 * @todo The sRGB colour space object should be implemented as a singleton.
68 * This is possible because it is thread-safe, and therefore it does
69 * not make sense to have more than one object of this class. At the
70 * same time, it is necessary that it implements the common interface
71 * of the colour space objects that are created on-the-fly from ICC
72 * profile files, therefore it cannot be static.
73 * As a consequence, translations within sRGB objects should always
74 * be dynamic instead of being done only once at instantiation time,
75 * because now the instantiation time is out of control of the library
76 * user. (And maybe even for ICC profiles we could provide ALL
77 * translations, be reading ALL possible translations at creating time
78 * and guarding them? Or would this be overkill?)
79 * The singleton pattern has special requirements
80 * for: 1) thread-safety. 2) dynamic libraries. See Wikipedia
81 * for details!
82 *
83 * @todo Is it possible to split this into an interface and into
84 * various implementations (a slow but safe implementation for
85 * all valid ICC files, and a fast optimized implementation for sRGB
86 * only? If so, is it possible to get rid of the dependency from
87 * LittleCMS by implementing sRGB ourselves, and providing ICC support
88 * via an optional header-only header that would link against LittleCMS
89 * without injecting this dependency into our shared library? And
90 * this might be faster! The web page
91 * https://bottosson.github.io/misc/colorpicker/#91a7ee is only
92 * JavaScript and works faster than this library! See
93 * https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ and
94 * http://www.brucelindbloom.com/index.html?Math.html and
95 * http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
96 * for implementation details.
97 *
98 * @todo We return double precision values. But doesn’t use LittleCMS
99 * only 16-bit-integer internally? On the other hand: Using double
100 * precision allows to filter out out-of-range values…
101 *
102 * @todo Do not convert QRgba64 to RgbDouble, but use a transform that
103 * reads QRgba64 directly. While the benefit might not be big in
104 * this function, in general it would be good to review for which data
105 * types we provide transforms and minimize conversions.
106 *
107 * @todo In the API of this class, clarify the precision. If more than
108 * 8 bit per channel, we have to switch from QRgb to QRgb64. But
109 * probably all OS APIs only accept 8 bit anyway? Is it worth the
110 * pain just because @ref ColorDialog can return <tt>QColor</tt>
111 * which provides 16 bit support?
112 *
113 * @todo Find more efficient ways of in-gamut detection. Maybe provide
114 * a subclass with optimized algorithms just for sRGB-build-in? */
115class RgbColorSpace : public QObject
116{
118
119 /** @brief The absolute file path of the profile.
120 *
121 * @note This is empty for build-in profiles.
122 *
123 * @sa READ @ref profileAbsoluteFilePath() const */
124 Q_PROPERTY(QString profileAbsoluteFilePath READ profileAbsoluteFilePath CONSTANT)
125
126 /** @brief The class of the profile.
127 *
128 * This type is declared as type to Qt’s type system via
129 * <tt>Q_DECLARE_METATYPE</tt>. Depending on your use case (for
130 * example if you want to use for <em>queued</em> signal-slot connections),
131 * you might consider calling <tt>qRegisterMetaType()</tt> for
132 * this type, once you have a QApplication object.
133 *
134 * @sa READ @ref profileClass() const */
135 Q_PROPERTY(cmsProfileClassSignature profileClass READ profileClass CONSTANT)
136
137 /** @brief The color model of the color space which is described by
138 * this profile.
139 *
140 * This type is declared as type to Qt’s type system via
141 * <tt>Q_DECLARE_METATYPE</tt>. Depending on your use case (for
142 * example if you want to use for <em>queued</em> signal-slot connections),
143 * you might consider calling <tt>qRegisterMetaType()</tt> for
144 * this type, once you have a QApplication object.
145 *
146 * @sa READ @ref profileColorModel() const */
147 Q_PROPERTY(cmsColorSpaceSignature profileColorModel READ profileColorModel CONSTANT)
148
149 /** @brief The copyright information of the profile.
150 *
151 * If supported by the underlying profile, this property is localized
152 * to the current locale <em>at the moment of the constructor call</em>.
153 *
154 * @note This is empty if the information is not available.
155 *
156 * @sa READ @ref profileCopyright() const */
157 Q_PROPERTY(QString profileCopyright READ profileCopyright CONSTANT)
158
159 /** @brief The date and time of the creation of the profile.
160 *
161 * @note This is null if the information is not available.
162 *
163 * @sa READ @ref profileCreationDateTime() const */
164 Q_PROPERTY(QDateTime profileCreationDateTime READ profileCreationDateTime CONSTANT)
165
166 /** @brief The file size of the profile, measured in byte.
167 *
168 * @note This is <tt>-1</tt> for build-in profiles.
169 *
170 * @sa READ @ref profileFileSize() const */
171 Q_PROPERTY(qint64 profileFileSize READ profileFileSize CONSTANT)
172
173 /** @brief Wether or not the profile has a color lookup table (CLUT).
174 *
175 * @sa READ @ref profileHasClut() const */
176 Q_PROPERTY(bool profileHasClut READ profileHasClut CONSTANT)
177
178 /** @brief Wether or not the profile has a matrix shaper.
179 *
180 * @sa READ @ref profileHasMatrixShaper() const */
181 Q_PROPERTY(bool profileHasMatrixShaper READ profileHasMatrixShaper CONSTANT)
182
183 /** @brief The ICC version of the profile.
184 *
185 * @sa READ @ref profileIccVersion() const */
186 Q_PROPERTY(QVersionNumber profileIccVersion READ profileIccVersion CONSTANT)
187
188 /** @brief The manufacturer information of the profile.
189 *
190 * If supported by the underlying profile, this property is localized
191 * to the current locale <em>at the moment of the constructor call</em>.
192 *
193 * @note This is empty if the information is not available.
194 *
195 * @sa READ @ref profileManufacturer() const */
196 Q_PROPERTY(QString profileManufacturer READ profileManufacturer CONSTANT)
197
198 /** @brief The maximum CIELch-D50 chroma of the profile.
199 *
200 * This value is equal or slightly bigger than the actual maximum chroma.
201 *
202 * @note This is the result of an auto-detection, which might theoretically
203 * in very rare cases return a value that is smaller than the actual
204 * maximum chroma.
205 *
206 * @sa READ @ref profileMaximumCielchD50Chroma() const */
207 Q_PROPERTY(double profileMaximumCielchD50Chroma READ profileMaximumCielchD50Chroma CONSTANT)
208
209 /** @brief The maximum Oklch chroma of the profile.
210 *
211 * This value is equal or slightly bigger than the actual maximum chroma.
212 *
213 * @note This is the result of an auto-detection, which might theoretically
214 * in very rare cases return a value that is smaller than the actual
215 * maximum chroma.
216 *
217 * @sa READ @ref profileMaximumOklchChroma() const */
218 Q_PROPERTY(double profileMaximumOklchChroma READ profileMaximumOklchChroma CONSTANT)
219
220 /** @brief The model information of the profile.
221 *
222 * If supported by the underlying profile, this property is localized
223 * to the current locale <em>at the moment of the constructor call</em>.
224 *
225 * @note This is empty if the information is not available.
226 *
227 * @sa READ @ref profileModel() const */
228 Q_PROPERTY(QString profileModel READ profileModel CONSTANT)
229
230 /** @brief The name of the profile.
231 *
232 * If supported by the underlying profile, this property is localized
233 * to the current locale <em>at the moment of the constructor call</em>.
234 *
235 * Note that this string might be very long in some profiles. On some
236 * UI elements, maybe it should be elided (truncate it and put “…” at
237 * the end).
238 *
239 * @note This is empty if the information is not available.
240 *
241 * @sa READ @ref profileName() const */
242 Q_PROPERTY(QString profileName READ profileName CONSTANT)
243
244 /** @brief The name of the profile.
245 *
246 * If supported by the underlying profile, this property is localized
247 * to the current locale <em>at the moment of the constructor call</em>.
248 *
249 * This type is declared as type to Qt’s type system via
250 * <tt>Q_DECLARE_METATYPE</tt>. Depending on your use case (for
251 * example if you want to use for <em>queued</em> signal-slot connections),
252 * you might consider calling <tt>qRegisterMetaType()</tt> for
253 * this type, once you have a QApplication object.
254 *
255 * @sa READ @ref profileName() const */
256 Q_PROPERTY(cmsColorSpaceSignature profilePcsColorModel READ profilePcsColorModel CONSTANT)
257
258public: // Static factory functions
259 [[nodiscard]] Q_INVOKABLE static QSharedPointer<PerceptualColor::RgbColorSpace> createFromFile(const QString &fileName);
260 [[nodiscard]] Q_INVOKABLE static QSharedPointer<PerceptualColor::RgbColorSpace> createSrgb();
261
262public:
263 virtual ~RgbColorSpace() noexcept override;
264 [[nodiscard]] Q_INVOKABLE virtual bool isCielabD50InGamut(const cmsCIELab &lab) const;
265 [[nodiscard]] Q_INVOKABLE virtual bool isCielchD50InGamut(const PerceptualColor::LchDouble &lch) const;
266 [[nodiscard]] Q_INVOKABLE virtual bool isOklchInGamut(const PerceptualColor::LchDouble &lch) const;
267 /** @brief Getter for property @ref profileAbsoluteFilePath
268 * @returns the property @ref profileAbsoluteFilePath */
269 [[nodiscard]] QString profileAbsoluteFilePath() const;
270 /** @brief Getter for property @ref profileClass
271 * @returns the property @ref profileClass */
272 [[nodiscard]] cmsProfileClassSignature profileClass() const;
273 /** @brief Getter for property @ref profileColorModel
274 * @returns the property @ref profileColorModel */
275 [[nodiscard]] cmsColorSpaceSignature profileColorModel() const;
276 /** @brief Getter for property @ref profileCopyright
277 * @returns the property @ref profileCopyright */
278 [[nodiscard]] QString profileCopyright() const;
279 /** @brief Getter for property @ref profileCreationDateTime
280 * @returns the property @ref profileCreationDateTime */
281 [[nodiscard]] QDateTime profileCreationDateTime() const;
282 /** @brief Getter for property @ref profileFileSize
283 * @returns the property @ref profileFileSize */
284 [[nodiscard]] qint64 profileFileSize() const;
285 /** @brief Getter for property @ref profileHasClut
286 * @returns the property @ref profileHasClut */
287 [[nodiscard]] bool profileHasClut() const;
288 /** @brief Getter for property @ref profileHasMatrixShaper
289 * @returns the property @ref profileHasMatrixShaper */
290 [[nodiscard]] bool profileHasMatrixShaper() const;
291 /** @brief Getter for property @ref profileIccVersion
292 * @returns the property @ref profileIccVersion */
293 [[nodiscard]] QVersionNumber profileIccVersion() const;
294 /** @brief Getter for property @ref profileManufacturer
295 * @returns the property @ref profileManufacturer */
296 [[nodiscard]] QString profileManufacturer() const;
297 /** @brief Getter for property @ref profileMaximumCielchD50Chroma
298 * @returns the property @ref profileMaximumCielchD50Chroma */
299 [[nodiscard]] double profileMaximumCielchD50Chroma() const;
300 /** @brief Getter for property @ref profileMaximumOklchChroma
301 * @returns the property @ref profileMaximumOklchChroma */
302 [[nodiscard]] double profileMaximumOklchChroma() const;
303 /** @brief Getter for property @ref profileModel
304 * @returns the property @ref profileModel */
305 [[nodiscard]] QString profileModel() const;
306 /** @brief Getter for property @ref profileName
307 * @returns the property @ref profileName */
308 [[nodiscard]] QString profileName() const;
309 /** @brief Getter for property @ref profilePcsColorModel
310 * @returns the property @ref profilePcsColorModel */
311 [[nodiscard]] cmsColorSpaceSignature profilePcsColorModel() const;
312 [[nodiscard]] Q_INVOKABLE virtual PerceptualColor::LchDouble reduceCielchD50ChromaToFitIntoGamut(const PerceptualColor::LchDouble &cielchD50color) const;
313 [[nodiscard]] Q_INVOKABLE virtual PerceptualColor::LchDouble reduceOklchChromaToFitIntoGamut(const PerceptualColor::LchDouble &oklchColor) const;
314 [[nodiscard]] Q_INVOKABLE virtual cmsCIELab toCielabD50(const QRgba64 rgbColor) const;
315 [[nodiscard]] Q_INVOKABLE virtual PerceptualColor::LchDouble toCielchD50Double(const QRgba64 rgbColor) const;
316 [[nodiscard]] Q_INVOKABLE virtual QRgb fromCielchD50ToQRgbBound(const PerceptualColor::LchDouble &lch) const;
317 [[nodiscard]] Q_INVOKABLE virtual QRgb fromCielabD50ToQRgbOrTransparent(const cmsCIELab &lab) const;
318 [[nodiscard]] Q_INVOKABLE virtual PerceptualColor::GenericColor fromCielchD50ToRgb1(const PerceptualColor::LchDouble &lch) const;
319
320private:
321 Q_DISABLE_COPY(RgbColorSpace)
322
323 /** @internal
324 *
325 * @brief Private constructor.
326 *
327 * @param parent The widget’s parent widget. This parameter will be
328 * passed to the base class’s constructor. */
329 explicit RgbColorSpace(QObject *parent = nullptr);
330
331 /** @internal
332 *
333 * @brief Declare the private implementation as friend class.
334 *
335 * This allows the private class to access the protected members and
336 * functions of instances of <em>this</em> class. */
337 friend class RgbColorSpacePrivate;
338 /** @brief Pointer to implementation (pimpl) */
339 ConstPropagatingUniquePointer<RgbColorSpacePrivate> d_pointer;
340
341 /** @internal @brief Only for unit tests. */
342 friend class TestRgbColorSpace;
343};
344
345} // namespace PerceptualColor
346
347#endif // RGBCOLORSPACE_H
The namespace of this library.
Q_INVOKABLEQ_INVOKABLE
Q_OBJECTQ_OBJECT
Q_PROPERTY(...)
QObject * parent() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:36 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.