Perceptual Color

rgbcolorspace_p.h
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4#ifndef RGBCOLORSPACE_P_H
5#define RGBCOLORSPACE_P_H
6
7// Include the header of the public class of this private implementation.
8// #include "rgbcolorspace.h"
9
10#include "cielchd50values.h"
11#include "constpropagatingrawpointer.h"
12#include "helperconstants.h"
13#include "oklchvalues.h"
14#include "rgbcolorspace.h"
15#include <lcms2.h>
16#include <optional>
17#include <qcontainerfwd.h>
18#include <qdatetime.h>
19#include <qglobal.h>
20#include <qlist.h>
21#include <qstring.h>
22#include <qversionnumber.h>
23
24namespace PerceptualColor
25{
26
27/** @internal
28 *
29 * @brief Private implementation within the <em>Pointer to
30 * implementation</em> idiom */
31class RgbColorSpacePrivate final
32{
33public:
34 /**
35 * @brief Enum class representing possible color spaces in the Lch color
36 * models.
37 */
38 enum class LchSpace {
39 Oklch, /**< The Oklch color space, which uses by definition
40 always a D65 whitepoint. */
41 CielchD50 /**< The CielchD50 color space, assuming a chromatic
42 adaption to the D50 whitepoint. */
43 };
44
45 explicit RgbColorSpacePrivate(RgbColorSpace *backLink);
46 /** @brief Default destructor
47 *
48 * The destructor is non-<tt>virtual</tt> because
49 * the class as a whole is <tt>final</tt>. */
50 ~RgbColorSpacePrivate() noexcept = default;
51
52 // Data members:
53 /**
54 * @brief All RGB colors located on the chromaticity boundary, ordered by
55 * CielchD50 hue.
56 *
57 * The chromaticity boundary consists of the spectral locus and the purple
58 * line.
59 *
60 * This dataset includes all integer RGB values (i.e., values representable
61 * with three one-byte channels) that lie on the chromaticity boundary.
62 * To ensure smooth cyclic transitions, the dataset includes duplicates
63 * of the lowest and highest hues positioned outside the [0, 360] boundary.
64 * For instance, the lowest original angle of 2° has a duplicate at 362°,
65 * and the highest original angle of 357° has a duplicate at -3°.
66 *
67 * The hue is normalized to the range [0, 360].
68 */
69 std::map<double, QColor> m_chromaticityBoundaryByCielchD50Hue360;
70 /**
71 * @brief All RGB colors located on the chromaticity boundary, ordered by
72 * Oklab hue.
73 *
74 * The chromaticity boundary consists of the spectral locus and the purple
75 * line.
76 *
77 * This dataset includes all integer RGB values (i.e., values representable
78 * with three one-byte channels) that lie on the chromaticity boundary.
79 * To ensure smooth cyclic transitions, the dataset includes duplicates
80 * of the lowest and highest hues positioned outside the [0, 360] boundary.
81 * For instance, the lowest original angle of 2° has a duplicate at 362°,
82 * and the highest original angle of 357° has a duplicate at -3°.
83 *
84 * The hue is normalized to the range [0, 360].
85 */
86 std::map<double, QColor> m_chromaticityBoundaryByOklabHue360;
87 /** @brief The darkest in-gamut point on the L* axis.
88 * @sa whitepointL
89 *
90 * @internal
91 *
92 * @todo Use cmsDetectBlackPoint? But “our” “blackpoint” is always on
93 * the grey axis, but the real blackpoint not? Document this? */
94 qreal m_cielabD50BlackpointL = 0;
95 /** @brief The lightest in-gamut point on the L* axis.
96 * @sa blackpointL() */
97 qreal m_cielabD50WhitepointL = 100;
98 /** @brief The darkest in-gamut point on the L* axis.
99 * @sa whitepointL
100 *
101 * @internal
102 *
103 * @todo Use cmsDetectBlackPoint? But “our” “blackpoint” is always on
104 * the grey axis, but the real blackpoint not? Document this? */
105 qreal m_oklabBlackpointL = 0;
106 /** @brief The lightest in-gamut point on the L* axis.
107 * @sa blackpointL() */
108 qreal m_oklabWhitepointL = 1;
109 /** @brief Internal storage for property
110 * @ref RgbColorSpace::profileAbsoluteFilePath */
111 QString m_profileAbsoluteFilePath;
112 /** @brief Internal storage for property
113 * @ref RgbColorSpace::profileClass */
114 cmsProfileClassSignature m_profileClass;
115 /** @brief Internal storage for property
116 * @ref RgbColorSpace::profileColorModel */
117 cmsColorSpaceSignature m_profileColorModel;
118 /** @brief Internal storage for property
119 * @ref RgbColorSpace::profileCopyright */
120 QString m_profileCopyright;
121 /** @brief Internal storage for property
122 * @ref RgbColorSpace::profileCreationDateTime */
123 QDateTime m_profileCreationDateTime;
124 /** @brief Internal storage for property
125 * @ref RgbColorSpace::profileFileSize */
126 qint64 m_profileFileSize = -1;
127 /** @brief Internal storage for property
128 * @ref RgbColorSpace::profileHasClut */
129 bool m_profileHasClut = false;
130 /** @brief Internal storage for property
131 * @ref RgbColorSpace::profileHasMatrixShaper */
132 bool m_profileHasMatrixShaper = false;
133 /** @brief Internal storage for property
134 * @ref RgbColorSpace::profileRenderingIntentDirections */
135 RgbColorSpace::RenderingIntentDirections m_profileRenderingIntentDirections;
136 /** @brief Internal storage for property
137 * @ref RgbColorSpace::profileIccVersion */
138 QVersionNumber m_profileIccVersion;
139 /** @brief Internal storage for property
140 * @ref RgbColorSpace::profileManufacturer */
141 QString m_profileManufacturer;
142 /** @brief Internal storage for property
143 * @ref RgbColorSpace::profileMaximumCielchD50Chroma */
144 double m_profileMaximumCielchD50Chroma = CielchD50Values::maximumChroma;
145 /** @brief Internal storage for property
146 * @ref RgbColorSpace::profileMaximumOklchChroma */
147 double m_profileMaximumOklchChroma = OklchValues::maximumChroma;
148 /** @brief Internal storage for property
149 * @ref RgbColorSpace::profileModel */
150 QString m_profileModel;
151 /** @brief Internal storage for property
152 * @ref RgbColorSpace::profileName */
153 QString m_profileName;
154 /** @brief Internal storage for property
155 * @ref RgbColorSpace::profilePcsColorModel */
156 cmsColorSpaceSignature m_profilePcsColorModel;
157 /** @brief Internal storage for property
158 * @ref RgbColorSpace::profileTagBlackpoint */
159 std::optional<cmsCIEXYZ> m_profileTagBlackpoint;
160 /** @brief Internal storage for property
161 * @ref RgbColorSpace::profileTagBluePrimary */
162 std::optional<cmsCIEXYZ> m_profileTagBluePrimary;
163 /** @brief Internal storage for property
164 * @ref RgbColorSpace::profileTagGreenPrimary */
165 std::optional<cmsCIEXYZ> m_profileTagGreenPrimary;
166 /** @brief Internal storage for property
167 * @ref RgbColorSpace::profileTagRedPrimary */
168 std::optional<cmsCIEXYZ> m_profileTagRedPrimary;
169 /** @brief Internal storage for property
170 * @ref RgbColorSpace::profileTagSignatures */
171 QStringList m_profileTagSignatures;
172 /** @brief Internal storage for property
173 * @ref RgbColorSpace::profileTagWhitepoint */
174 std::optional<cmsCIEXYZ> m_profileTagWhitepoint;
175 /** @brief A handle to a LittleCMS transform. */
176 cmsHTRANSFORM m_transformCielabD50ToRgb16Handle = nullptr;
177 /** @brief A handle to a LittleCMS transform. */
178 cmsHTRANSFORM m_transformCielabD50ToRgbHandle = nullptr;
179 /** @brief A handle to a LittleCMS transform. */
180 cmsHTRANSFORM m_transformRgbToCielabD50Handle = nullptr;
181
182 // Functions:
183 static void deleteTransform(cmsHTRANSFORM *transformHandle);
184 void initializeChromaticityBoundaries();
185 [[nodiscard]] bool initialize(cmsHPROFILE rgbProfileHandle);
186 [[nodiscard]] Q_INVOKABLE QColor maxChromaColorByHue360(double oklabHue360, RgbColorSpacePrivate::LchSpace type) const;
187 [[nodiscard]] static QDateTime profileCreationDateTime(cmsHPROFILE profileHandle);
188 [[nodiscard]] static QVersionNumber profileIccVersion(cmsHPROFILE profileHandle);
189 [[nodiscard]] static QString profileInformation(cmsHPROFILE profileHandle, cmsInfoType infoType, const QString &languageTerritory);
190 [[nodiscard]] static std::optional<cmsCIEXYZ> profileReadCmsciexyzTag(cmsHPROFILE profileHandle, cmsTagSignature signature);
191 [[nodiscard]] static QStringList profileTagSignatures(cmsHPROFILE profileHandle);
192
193 /** @brief Increment factor for the maximum-chroma detection.
194 *
195 * The maximum-chroma detection, regardless of the precision, might
196 * always return a value that is a bit too small. However, we want
197 * to have @ref RgbColorSpace::profileMaximumCielchD50Chroma and
198 * @ref RgbColorSpace::profileMaximumOklchChroma values that
199 * are equal or slightly bigger than the actual maximum-chroma,
200 * to make sure to not exclude valid values. Therefore,
201 * @ref initializeChromaticityBoundaries() uses this increment factor
202 * to slightly increment the outcome of the chroma detection relative
203 * to the original value, as a safety margin. Note that additionally,
204 * an absolute increment should also be added, because of limited
205 * precision in floating point operations. */
206 static constexpr double chromaDetectionIncrementFactor = 1.02;
207 /** @brief For detecting CIELab in-gamut or out-of-gamut colors.
208 *
209 * For gamut detection, a roundtrip conversion is performed: Lab values
210 * are converted to an RGB color space and backwards. If the distance
211 * in euclidean space between the the original Lab value and the result
212 * of the roundtrip is smaller than a certain value, it is considered
213 * as an in-gamut value.
214 *
215 * This deviation limit should be as small as possible for a more correct
216 * gamut boundary. But it must unfortunately also be big enough to ignore
217 * rounding errors. The current value was chosen by trial-and-error. */
218 static constexpr qreal cielabDeviationLimit = 0.5;
219 /** @brief For detecting Oklab in-gamut or out-of-gamut colors.
220 *
221 * For gamut detection, a roundtrip conversion is performed: Lab values
222 * are converted to an RGB color space and backwards. If the distance
223 * in euclidean space between the the original Lab value and the result
224 * of the roundtrip is smaller than a certain value, it is considered
225 * as an in-gamut value.
226 *
227 * This deviation limit should be as small as possible for a more correct
228 * gamut boundary. But it must unfortunately also be big enough to ignore
229 * rounding errors. The current value was chosen by trial-and-error. */
230 static constexpr qreal oklabDeviationLimit = 0.001;
231
232private:
233 Q_DISABLE_COPY(RgbColorSpacePrivate)
234
235 /** @brief Pointer to the object from which <em>this</em> object
236 * is the private implementation. */
237 ConstPropagatingRawPointer<RgbColorSpace> q_pointer;
238};
239
240} // namespace PerceptualColor
241
242#endif // RGBCOLORSPACE_P_H
The namespace of this library.
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 12:03:13 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.