Krita Source Code Documentation
Loading...
Searching...
No Matches
KisCanvasSurfaceColorSpaceManager.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2025 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <KoColorProfile.h>
11
12#include <kis_config.h>
13
14#include <KisDisplayConfig.h>
16
19
21{
22 Private(KisSurfaceColorManagerInterface *interface) : interface(interface) {}
23
24 QScopedPointer<KisSurfaceColorManagerInterface> interface;
26 std::optional<KisSurfaceColorimetry::RenderIntent> proofingIntentOverride;
28
30};
31
33 const KisConfig::CanvasSurfaceMode surfaceMode,
34 const KisDisplayConfig::Options &options,
35 QObject *parent)
36 : QObject(parent)
37 , m_d(new Private(interface))
38{
39 m_d->currentConfig.profile = KoColorSpaceRegistry::instance()->p709SRGBProfile();
40 m_d->currentConfig.setOptions(options);
41 m_d->currentConfig.isHDR = false;
42 m_d->surfaceMode = surfaceMode;
43
46
47 if (m_d->interface->isReady()) {
49 }
50}
51
55
57{
58 QString report;
59 QDebug str(&report);
60
61 if (!m_d->interface->isReady()) {
62 str << "WARNING: surface color management interface is not ready!" << Qt::endl;
63 str << Qt::endl;
64 }
65
70
71 str << "Configured mode:" << m_d->surfaceMode << Qt::endl;
72
73 RenderIntent preferredIntent = Private::calculateConfigIntent(m_d->currentConfig.options());
74 str << "Configured intent:" << preferredIntent << "supported:" << m_d->interface->supportsRenderIntent(preferredIntent) << Qt::endl;
75
76 str << "Actual intent:";
77 if (m_d->interface->renderingIntent()) {
78 str << *m_d->interface->renderingIntent() << Qt::endl;
79 } else {
80 str << "<none>" << Qt::endl;
81 }
82 str << Qt::endl;
83
84 str << "Active surface description:";
85 if (m_d->interface->surfaceDescription()) {
86 str << Qt::endl;
87 str.noquote() << m_d->interface->surfaceDescription()->makeTextReport() << Qt::endl;
88 } else {
89 str << "<none>" << Qt::endl;
90 }
91 str << Qt::endl;
92
93 str << "Selected Profile:";
94 if (m_d->currentConfig.profile) {
95 auto profile = m_d->currentConfig.profile;
96
97 str << profile->name() << Qt::endl;
98 str << " primaries:" << KoColorProfile::getColorPrimariesName(profile->getColorPrimaries()) << Qt::endl;
99 str << " transfer: " << KoColorProfile::getTransferCharacteristicName(profile->getTransferCharacteristics()) << Qt::endl;
100 str << Qt::endl;
101
102 {
103 auto colVec = profile->getColorantsxyY();
104 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(colVec.size() == 9, report);
105 KisColorimetryUtils::xyY colR{colVec[0], colVec[1], colVec[2]};
106 KisColorimetryUtils::xyY colG{colVec[3], colVec[4], colVec[5]};
107 KisColorimetryUtils::xyY colB{colVec[6], colVec[7], colVec[8]};
108
109 str << " red: " << colR << Qt::endl;
110 str << " green:" << colG << Qt::endl;
111 str << " blue: " << colB << Qt::endl;
112 }
113
114 {
115 auto whiteVec = profile->getWhitePointxyY();
116 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(whiteVec.size() == 3, report);
117 KisColorimetryUtils::xyY white{whiteVec[0], whiteVec[1], whiteVec[2]};
118
119 str << " white: " << white << Qt::endl;
120 str << Qt::endl;
121 }
122
123 } else {
124 str << "<none>" << Qt::endl;
125 }
126
127 str << "Compositor preferred surface description:";
128 if (m_d->interface->preferredSurfaceDescription()) {
129 str << Qt::endl;
130 str.noquote() << m_d->interface->preferredSurfaceDescription()->makeTextReport() << Qt::endl;
131 } else {
132 str << "<none>" << Qt::endl;
133 }
134 str << Qt::endl;
135
136 return report;
137}
138
140{
141 QString report;
142 QDebug str(&report);
143
144 if (!m_d->interface->isReady()) {
145 str << "WARNING: surface color management interface is not ready!" << Qt::endl;
146 str << Qt::endl;
147 }
148
149 if (m_d->interface->preferredSurfaceDescription()) {
150 str << Qt::endl;
151 str.noquote() << m_d->interface->preferredSurfaceDescription()->makeTextReport() << Qt::endl;
152 } else {
153 str << "<none>" << Qt::endl;
154 }
155
156 return report;
157}
158
159KisSurfaceColorimetry::RenderIntent KisCanvasSurfaceColorSpaceManager::Private::calculateConfigIntent(const KisDisplayConfig::Options &options)
160{
162
163 RenderIntent intent = RenderIntent::render_intent_perceptual;
164
165 switch (options.first) {
167 // default value
168 break;
170 intent =
172 RenderIntent::render_intent_relative_bpc :
173 RenderIntent::render_intent_relative;
174 break;
176 intent =
177 RenderIntent::render_intent_saturation;
178 break;
180 intent =
181 RenderIntent::render_intent_absolute;
182 break;
183 }
184 return intent;
185}
186
188{
189 if (!m_d->interface->isReady()) return;
190 if (surfaceMode == m_d->surfaceMode && options == m_d->currentConfig.options()) return;
191
192 m_d->surfaceMode = surfaceMode;
194}
195
200
202{
203 if (isReady) {
204 reinitializeSurfaceDescription(m_d->currentConfig.options());
205 }
206}
207
212
213#include <KoColorProfile.h>
215
217{
222
223 RenderIntent preferredIntent =
224 Private::calculateConfigIntent(newOptions);
225
226 if (!m_d->interface->supportsRenderIntent(preferredIntent)) {
227 qWarning() << "WARNING: failed to set user preferred rendering"
228 << "intent for the surface, intent \""
229 << preferredIntent << "\" is unsupported, falling back to \"perceptual\"";
230
231 preferredIntent = RenderIntent::render_intent_perceptual;
232
233 // perceptual intent is guaranteed to be supported by the compositor
234 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->interface->supportsRenderIntent(preferredIntent));
235 }
236
237 std::optional<SurfaceDescription> requestedDescription;
238 const KoColorProfile *profile = nullptr;
239
240 if (m_d->surfaceMode == KisConfig::CanvasSurfaceMode::Unmanaged) {
242 } else {
243 requestedDescription = SurfaceDescription();
244
245 using namespace KisSurfaceColorimetry;
246
247 const auto compositorPreferred = m_d->interface->preferredSurfaceDescription();
248 KIS_SAFE_ASSERT_RECOVER_RETURN(compositorPreferred);
249
250 // we have our own definition of the Rec2020PQ space with
251 // the reference point fixed to 80 cd/m2
252 auto makeKritaRec2020PQLuminance = [] () {
253 Luminance luminance;
254 luminance.minLuminance = 0;
255 luminance.referenceLuminance = 80;
256 luminance.maxLuminance = 10000;
257 return luminance;
258 };
259
260 if (m_d->surfaceMode == KisConfig::CanvasSurfaceMode::Preferred) {
261
262 if (compositorPreferred->colorSpace.isHDR()) {
263 // we support HDR only via Rec2020PQ, so reset the color space to that
264 // (KWin 6.5+ reports gamma-2.2 as preferred color space for whatever
265 // reason)
266 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_bt2020;
267 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_st2084_pq;
268 } else {
269 // we don't copy mastering properties, since they mean a different thing
270 // for the preferred space and outputs
271 requestedDescription->colorSpace = compositorPreferred->colorSpace;
272 }
273
274 if (std::holds_alternative<NamedTransferFunction>(requestedDescription->colorSpace.transferFunction) &&
275 std::get<NamedTransferFunction>(requestedDescription->colorSpace.transferFunction) == NamedTransferFunction::transfer_function_st2084_pq) {
276
277 requestedDescription->colorSpace.luminance = makeKritaRec2020PQLuminance();
278 }
279 } else if (m_d->surfaceMode == KisConfig::CanvasSurfaceMode::Rec2020pq) {
280 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_bt2020;
281 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_st2084_pq;
282 requestedDescription->colorSpace.luminance = makeKritaRec2020PQLuminance();
283 } else if (m_d->surfaceMode == KisConfig::CanvasSurfaceMode::Rec709g22) {
284 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_srgb;
285 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_gamma22;
286 if (compositorPreferred->colorSpace.luminance) {
287 // rec709-g22 is considered as SDR in lcms, so we should our space to SDR range
288 requestedDescription->colorSpace.luminance =
289 compositorPreferred->colorSpace.luminance->clipToSdr();
290 }
291 } else if (m_d->surfaceMode == KisConfig::CanvasSurfaceMode::Rec709g10) {
292 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_srgb;
293 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_ext_linear;
294 if (compositorPreferred->colorSpace.luminance) {
307 requestedDescription->colorSpace.luminance =
308 compositorPreferred->colorSpace.luminance->clipToSdr();
309 }
310 }
311
312 if (std::holds_alternative<NamedPrimaries>(requestedDescription->colorSpace.primaries) &&
313 std::get<NamedPrimaries>(requestedDescription->colorSpace.primaries) == NamedPrimaries::primaries_unknown) {
314
315 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_srgb;
316 }
317
318 if (std::holds_alternative<NamedTransferFunction>(requestedDescription->colorSpace.transferFunction) &&
319 std::get<NamedTransferFunction>(requestedDescription->colorSpace.transferFunction) == NamedTransferFunction::transfer_function_unknown) {
320
321 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_gamma22;
322 }
323
324 if (!m_d->interface->supportsSurfaceDescription(*requestedDescription)) {
325
326 // try pure sRGB
327 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_srgb;
328 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_srgb;
329
330 if (!m_d->interface->supportsSurfaceDescription(*requestedDescription)) {
331 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_gamma22;
332
333 if (!m_d->interface->supportsSurfaceDescription(*requestedDescription)) {
334 qWarning() << "WARNING: failed to find a suitable surface format for the compositor";
335 return; // TODO: extra signals?
336 }
337 }
338 }
339
340 {
341 auto request = colorSpaceToRequest(requestedDescription->colorSpace);
342 if (request.isValid()) {
343 profile = KoColorSpaceRegistry::instance()->profileFor(request.colorants,
344 request.colorPrimariesType,
345 request.transferFunction);
346 }
347 }
348
349 if (!profile) {
350 // keep primaries, but change the transfer function to gamma22
351 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_gamma22;
352 if (m_d->interface->supportsSurfaceDescription(*requestedDescription)) {
353 auto request = colorSpaceToRequest(requestedDescription->colorSpace);
354 if (request.isValid()) {
355 profile = KoColorSpaceRegistry::instance()->profileFor(request.colorants,
356 request.colorPrimariesType,
357 request.transferFunction);
358 }
359 }
360 }
361
362 if (!profile) {
363 // keep primaries, but change the transfer function to srgb
364 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_srgb;
365 if (m_d->interface->supportsSurfaceDescription(*requestedDescription)) {
366 auto request = colorSpaceToRequest(requestedDescription->colorSpace);
367 if (request.isValid()) {
368 profile = KoColorSpaceRegistry::instance()->profileFor(request.colorants,
369 request.colorPrimariesType,
370 request.transferFunction);
371 }
372 }
373 }
374
375 if (!profile) {
376 qWarning() << "WARNING: failed to to create a profile for the compositor's preferred color space";
377 qWarning() << " " << ppVar(compositorPreferred);
378 qWarning() << " " << ppVar(*requestedDescription);
379 // TODO: debug all supported values for the compositor
380 return; // TODO: extra signals?
381 }
382 }
383
385 KIS_SAFE_ASSERT_RECOVER_RETURN(!requestedDescription || m_d->interface->supportsSurfaceDescription(*requestedDescription));
386
387 if (m_d->interface->surfaceDescription() != requestedDescription ||
388 m_d->interface->renderingIntent() != preferredIntent) {
389
390 if (requestedDescription) {
391 auto future = m_d->interface->setSurfaceDescription(*requestedDescription, preferredIntent);
392 future.then([](QFuture<bool> result) {
393 if (!result.isValid() || !result.result()) {
394 qWarning()
395 << "WARNING: failed to set color space for the surface, setSurfaceDescription() returned false";
396 }
397 });
398 } else {
399 m_d->interface->unsetSurfaceDescription();
400 }
401 }
402
403 const bool requestedDescriptionIsHDR = requestedDescription && requestedDescription->colorSpace.isHDR();
404
405 KisDisplayConfig newDisplayConfig;
406 newDisplayConfig.profile = profile;
407 newDisplayConfig.setOptions(newOptions); // TODO: think about failure to set the intent and infinite update loop
408 newDisplayConfig.isHDR = requestedDescriptionIsHDR;
409
410 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->currentConfig.profile);
411
412 if (m_d->currentConfig != newDisplayConfig) {
413 m_d->currentConfig = newDisplayConfig;
414 Q_EMIT sigDisplayConfigChanged(m_d->currentConfig);
415 }
416}
417
419{
420 return m_d->interface->isReady();
421}
422
424{
425 return m_d->currentConfig;
426}
KisDisplayConfig This class keeps track of the color management configuration for image to display....
void setOptions(const Options &options)
std::pair< KoColorConversionTransformation::Intent, KoColorConversionTransformation::ConversionFlags > Options
const KoColorProfile * profile
void sigReadyChanged(bool value)
void sigPreferredSurfaceDescriptionChanged(const KisSurfaceColorimetry::SurfaceDescription &desc)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define ppVar(var)
Definition kis_debug.h:155
#define INTENT_ABSOLUTE_COLORIMETRIC
Definition kis_global.h:106
#define INTENT_PERCEPTUAL
Definition kis_global.h:103
#define INTENT_RELATIVE_COLORIMETRIC
Definition kis_global.h:104
#define INTENT_SATURATION
Definition kis_global.h:105
QScopedPointer< KisSurfaceColorManagerInterface > interface
KisCanvasSurfaceColorSpaceManager(KisSurfaceColorManagerInterface *interface, const KisConfig::CanvasSurfaceMode surfaceMode, const KisDisplayConfig::Options &options, QObject *parent=nullptr)
void sigDisplayConfigChanged(const KisDisplayConfig &config)
Private(KisSurfaceColorManagerInterface *interface)
std::optional< KisSurfaceColorimetry::RenderIntent > proofingIntentOverride
void reinitializeSurfaceDescription(const KisDisplayConfig::Options &newOptions)
static KisSurfaceColorimetry::RenderIntent calculateConfigIntent(const KisDisplayConfig::Options &options)
void setDisplayConfigOptions(const KisConfig::CanvasSurfaceMode surfaceMode, const KisDisplayConfig::Options &options)
static QString getTransferCharacteristicName(TransferCharacteristics curve)
getTransferCharacteristicName
static QString getColorPrimariesName(ColorPrimaries primaries)
getColorPrimariesName
const KoColorProfile * profileFor(const QVector< double > &colorants, ColorPrimaries colorPrimaries, TransferCharacteristics transferFunction) const
profileFor tries to find the profile that matches these characteristics, if no such profile is found,...
static KoColorSpaceRegistry * instance()
const KoColorProfile * p709SRGBProfile() const