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
20namespace {
21struct SurfaceFormatSelectionResult {
22 const KoColorProfile *profile = nullptr;
23 std::optional<KisSurfaceColorimetry::SurfaceDescription> requestedDescription;
24 QString errorMessage; // empty when no error
25};
26} /* namespace */
27
29{
30 Private(KisSurfaceColorManagerInterface *interface) : interface(interface) {}
31
32 QScopedPointer<KisSurfaceColorManagerInterface> interface;
34 std::optional<KisSurfaceColorimetry::RenderIntent> proofingIntentOverride;
37
39
40 SurfaceFormatSelectionResult
42};
43
45 const KisConfig::CanvasSurfaceMode surfaceMode,
46 const KisDisplayConfig::Options &options,
47 QObject *parent)
48 : QObject(parent)
49 , m_d(new Private(interface))
50{
51 m_d->currentConfig.profile = KoColorSpaceRegistry::instance()->p709SRGBProfile();
52 m_d->currentConfig.setOptions(options);
53 m_d->currentConfig.isHDR = false;
54 m_d->surfaceMode = surfaceMode;
55
58
59 if (m_d->interface->isReady()) {
61 }
62}
63
67
69{
70 QString report;
71 QDebug str(&report);
72
73 if (!m_d->interface->isReady()) {
74 str << "WARNING: surface color management interface is not ready!" << Qt::endl;
75 str << Qt::endl;
76 }
77
82
83 if (!m_d->lastErrorString.isEmpty()) {
84 str.noquote().nospace()
85 << "ERROR: Failed to set up color management for the surface: "
86 << m_d->lastErrorString
87 << Qt::endl;
88 str << Qt::endl;
89 }
90
91 str << "Configured mode:" << m_d->surfaceMode << Qt::endl;
92
93 RenderIntent preferredIntent = Private::calculateConfigIntent(m_d->currentConfig.options());
94 str << "Configured intent:" << preferredIntent << "supported:" << m_d->interface->supportsRenderIntent(preferredIntent) << Qt::endl;
95
96 str << "Actual intent:";
97 if (m_d->interface->renderingIntent()) {
98 str << *m_d->interface->renderingIntent() << Qt::endl;
99 } else {
100 str << "<none>" << Qt::endl;
101 }
102 str << Qt::endl;
103
104 str << "Active surface description:";
105 if (m_d->interface->surfaceDescription()) {
106 str << Qt::endl;
107 str.noquote() << m_d->interface->surfaceDescription()->makeTextReport() << Qt::endl;
108 } else {
109 str << "<none>" << Qt::endl;
110 }
111 str << Qt::endl;
112
113 str << "Selected Profile:";
114 if (m_d->currentConfig.profile) {
115 auto profile = m_d->currentConfig.profile;
116
117 str << profile->name() << Qt::endl;
118 str << " primaries:" << KoColorProfile::getColorPrimariesName(profile->getColorPrimaries()) << Qt::endl;
119 str << " transfer: " << KoColorProfile::getTransferCharacteristicName(profile->getTransferCharacteristics()) << Qt::endl;
120 str << Qt::endl;
121
122 {
123 auto colVec = profile->getColorantsxyY();
124 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(colVec.size() == 9, report);
125 KisColorimetryUtils::xyY colR{colVec[0], colVec[1], colVec[2]};
126 KisColorimetryUtils::xyY colG{colVec[3], colVec[4], colVec[5]};
127 KisColorimetryUtils::xyY colB{colVec[6], colVec[7], colVec[8]};
128
129 str << " red: " << colR << Qt::endl;
130 str << " green:" << colG << Qt::endl;
131 str << " blue: " << colB << Qt::endl;
132 }
133
134 {
135 auto whiteVec = profile->getWhitePointxyY();
136 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(whiteVec.size() == 3, report);
137 KisColorimetryUtils::xyY white{whiteVec[0], whiteVec[1], whiteVec[2]};
138
139 str << " white: " << white << Qt::endl;
140 str << Qt::endl;
141 }
142
143 } else {
144 str << "<none>" << Qt::endl;
145 }
146
147 str << "Compositor preferred surface description:";
148 if (m_d->interface->preferredSurfaceDescription()) {
149 str << Qt::endl;
150 str.noquote() << m_d->interface->preferredSurfaceDescription()->makeTextReport() << Qt::endl;
151 } else {
152 str << "<none>" << Qt::endl;
153 }
154 str << Qt::endl;
155
156 return report;
157}
158
160{
161 QString report;
162 QDebug str(&report);
163
164 if (!m_d->interface->isReady()) {
165 str << "WARNING: surface color management interface is not ready!" << Qt::endl;
166 str << Qt::endl;
167 }
168
169 if (m_d->interface->preferredSurfaceDescription()) {
170 str << Qt::endl;
171 str.noquote() << m_d->interface->preferredSurfaceDescription()->makeTextReport() << Qt::endl;
172 } else {
173 str << "<none>" << Qt::endl;
174 }
175
176 return report;
177}
178
179KisSurfaceColorimetry::RenderIntent KisCanvasSurfaceColorSpaceManager::Private::calculateConfigIntent(const KisDisplayConfig::Options &options)
180{
182
183 RenderIntent intent = RenderIntent::render_intent_perceptual;
184
185 switch (options.first) {
187 // default value
188 break;
190 intent =
192 RenderIntent::render_intent_relative_bpc :
193 RenderIntent::render_intent_relative;
194 break;
196 intent =
197 RenderIntent::render_intent_saturation;
198 break;
200 intent =
201 RenderIntent::render_intent_absolute;
202 break;
203 }
204 return intent;
205}
206
208{
209 if (!m_d->interface->isReady()) return;
210 if (surfaceMode == m_d->surfaceMode && options == m_d->currentConfig.options()) return;
211
212 m_d->surfaceMode = surfaceMode;
214}
215
220
222{
223 if (isReady) {
224 reinitializeSurfaceDescription(m_d->currentConfig.options());
225 }
226}
227
232
233#include <KoColorProfile.h>
235
236SurfaceFormatSelectionResult
237KisCanvasSurfaceColorSpaceManager::Private::
238selectSurfaceDescription(KisConfig::CanvasSurfaceMode requestedSurfaceMode, const KisSurfaceColorimetry::SurfaceDescription &compositorPreferred)
239{
245
246 std::optional<SurfaceDescription> requestedDescription = SurfaceDescription();
247 const KoColorProfile *profile = nullptr;
248
249 // we have our own definition of the Rec2020PQ space with
250 // the reference point fixed to 80 cd/m2
251 auto makeKritaRec2020PQLuminance = []() {
252 Luminance luminance;
253 luminance.minLuminance = 0;
254 luminance.referenceLuminance = 80;
255 luminance.maxLuminance = 10000;
256 return luminance;
257 };
258
259 if (requestedSurfaceMode == KisConfig::CanvasSurfaceMode::Preferred) {
260 if (compositorPreferred.colorSpace.isHDR()) {
261 // we support HDR only via Rec2020PQ, so reset the color space to that
262 // (KWin 6.5+ reports gamma-2.2 as preferred color space for whatever
263 // reason)
264 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_bt2020;
265 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_st2084_pq;
266 } else {
267 // we don't copy mastering properties, since they mean a different thing
268 // for the preferred space and outputs
269 requestedDescription->colorSpace = compositorPreferred.colorSpace;
270 }
271
272 if (std::holds_alternative<NamedTransferFunction>(requestedDescription->colorSpace.transferFunction)
273 && std::get<NamedTransferFunction>(requestedDescription->colorSpace.transferFunction)
274 == NamedTransferFunction::transfer_function_st2084_pq) {
275 requestedDescription->colorSpace.luminance = makeKritaRec2020PQLuminance();
276 }
277 } else if (requestedSurfaceMode == KisConfig::CanvasSurfaceMode::Rec2020pq) {
278 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_bt2020;
279 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_st2084_pq;
280 requestedDescription->colorSpace.luminance = makeKritaRec2020PQLuminance();
281 } else if (requestedSurfaceMode == KisConfig::CanvasSurfaceMode::Rec709g22) {
282 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_srgb;
283 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_gamma22;
284 if (compositorPreferred.colorSpace.luminance) {
285 // rec709-g22 is considered as SDR in lcms, so we should our space to SDR range
286 requestedDescription->colorSpace.luminance = compositorPreferred.colorSpace.luminance->clipToSdr();
287 }
288 } else if (requestedSurfaceMode == KisConfig::CanvasSurfaceMode::Rec709g10) {
289 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_srgb;
290 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_ext_linear;
291 if (compositorPreferred.colorSpace.luminance) {
304 requestedDescription->colorSpace.luminance = compositorPreferred.colorSpace.luminance->clipToSdr();
305 }
306 }
307
308 if (std::holds_alternative<NamedPrimaries>(requestedDescription->colorSpace.primaries)
309 && std::get<NamedPrimaries>(requestedDescription->colorSpace.primaries) == NamedPrimaries::primaries_unknown) {
310 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_srgb;
311 }
312
313 if (std::holds_alternative<NamedTransferFunction>(requestedDescription->colorSpace.transferFunction)
314 && std::get<NamedTransferFunction>(requestedDescription->colorSpace.transferFunction)
315 == NamedTransferFunction::transfer_function_unknown) {
316 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_gamma22;
317 }
318
319 if (!this->interface->supportsSurfaceDescription(*requestedDescription)) {
320 // try pure sRGB
321 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_srgb;
322 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_srgb;
323
324 if (!this->interface->supportsSurfaceDescription(*requestedDescription)) {
325 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_gamma22;
326
327 if (!this->interface->supportsSurfaceDescription(*requestedDescription)) {
328 QString errorMessage = "failed to find a suitable surface format for the compositor";
329 qWarning().nospace().noquote() << "ERROR: " << errorMessage;
330 return {KoColorSpaceRegistry::instance()->p709SRGBProfile(), std::nullopt, errorMessage};
331 }
332 }
333 }
334
335 {
336 auto request = colorSpaceToRequest(requestedDescription->colorSpace);
337 if (request.isValid()) {
338 profile = KoColorSpaceRegistry::instance()->profileFor(request.colorants,
339 request.colorPrimariesType,
340 request.transferFunction);
341 }
342 }
343
344 if (!profile) {
345 // keep primaries, but change the transfer function to gamma22
346 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_gamma22;
347 if (this->interface->supportsSurfaceDescription(*requestedDescription)) {
348 auto request = colorSpaceToRequest(requestedDescription->colorSpace);
349 if (request.isValid()) {
350 profile = KoColorSpaceRegistry::instance()->profileFor(request.colorants,
351 request.colorPrimariesType,
352 request.transferFunction);
353 }
354 }
355 }
356
357 if (!profile) {
358 // try resetting primaries to srgb
359 requestedDescription->colorSpace.primaries = NamedPrimaries::primaries_srgb;
360 if (this->interface->supportsSurfaceDescription(*requestedDescription)) {
361 auto request = colorSpaceToRequest(requestedDescription->colorSpace);
362 if (request.isValid()) {
363 profile = KoColorSpaceRegistry::instance()->profileFor(request.colorants,
364 request.colorPrimariesType,
365 request.transferFunction);
366 }
367 }
368 }
369
370 if (!profile) {
371 // now change the transfer function to srgb
372 requestedDescription->colorSpace.transferFunction = NamedTransferFunction::transfer_function_srgb;
373 if (this->interface->supportsSurfaceDescription(*requestedDescription)) {
374 auto request = colorSpaceToRequest(requestedDescription->colorSpace);
375 if (request.isValid()) {
376 profile = KoColorSpaceRegistry::instance()->profileFor(request.colorants,
377 request.colorPrimariesType,
378 request.transferFunction);
379 }
380 }
381 }
382
383 if (!profile) {
384 QString errorMessage = "failed to create a profile for the compositor's preferred color space";
385 qWarning().nospace().noquote() << "ERROR: " << errorMessage;
386 qWarning() << " " << ppVar(compositorPreferred);
387 qWarning() << " " << ppVar(*requestedDescription);
388 return {KoColorSpaceRegistry::instance()->p709SRGBProfile(), std::nullopt, errorMessage};
389 }
390
391 return {profile, requestedDescription, {}};
392}
393
395{
400
401 RenderIntent preferredIntent =
402 Private::calculateConfigIntent(newOptions);
403
404 if (!m_d->interface->supportsRenderIntent(preferredIntent)) {
405 qWarning() << "WARNING: failed to set user preferred rendering"
406 << "intent for the surface, intent \""
407 << preferredIntent << "\" is unsupported, falling back to \"perceptual\"";
408
409 preferredIntent = RenderIntent::render_intent_perceptual;
410
411 // perceptual intent is guaranteed to be supported by the compositor
412 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->interface->supportsRenderIntent(preferredIntent));
413 }
414
415 std::optional<SurfaceDescription> requestedDescription;
416 const KoColorProfile *profile = nullptr;
417
418 if (m_d->surfaceMode == KisConfig::CanvasSurfaceMode::Unmanaged) {
420 m_d->lastErrorString.clear();
421 } else {
422 const auto compositorPreferred = m_d->interface->preferredSurfaceDescription();
423 KIS_SAFE_ASSERT_RECOVER_RETURN(compositorPreferred);
424
425 auto result = m_d->selectSurfaceDescription(m_d->surfaceMode, *compositorPreferred);
426 profile = result.profile;
427 requestedDescription = result.requestedDescription;
428 m_d->lastErrorString = result.errorMessage;
429 }
430
432 KIS_SAFE_ASSERT_RECOVER_RETURN(!requestedDescription || m_d->interface->supportsSurfaceDescription(*requestedDescription));
433
434 if (m_d->interface->surfaceDescription() != requestedDescription ||
435 m_d->interface->renderingIntent() != preferredIntent) {
436
437 if (requestedDescription) {
438 auto future = m_d->interface->setSurfaceDescription(*requestedDescription, preferredIntent);
439 future.then([this](QFuture<bool> result) {
440 if (!result.isValid() || !result.result()) {
441 QString errorMessage = "failed to set color space for the surface, setSurfaceDescription() returned false";
442 m_d->lastErrorString = errorMessage;
443 qWarning().nospace().noquote() << "ERROR: " << errorMessage;
444 }
445 });
446 } else {
447 m_d->interface->unsetSurfaceDescription();
448 }
449 }
450
451 const bool requestedDescriptionIsHDR = requestedDescription && requestedDescription->colorSpace.isHDR();
452
453 KisDisplayConfig newDisplayConfig;
454 newDisplayConfig.profile = profile;
455 newDisplayConfig.setOptions(newOptions); // TODO: think about failure to set the intent and infinite update loop
456 newDisplayConfig.isHDR = requestedDescriptionIsHDR;
457
458 KIS_SAFE_ASSERT_RECOVER(m_d->currentConfig.profile) {
459 m_d->currentConfig.profile = KoColorSpaceRegistry::instance()->p709SRGBProfile();
460 }
461
462 if (m_d->currentConfig != newDisplayConfig) {
463 m_d->currentConfig = newDisplayConfig;
464 Q_EMIT sigDisplayConfigChanged(m_d->currentConfig);
465 }
466}
467
469{
470 return m_d->interface->isReady();
471}
472
474{
475 return m_d->currentConfig;
476}
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(cond)
Definition kis_assert.h:126
#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
PigmentProfileRequest colorSpaceToRequest(ColorSpace cs)
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)
SurfaceFormatSelectionResult selectSurfaceDescription(KisConfig::CanvasSurfaceMode surfaceMode, const KisSurfaceColorimetry::SurfaceDescription &compositorPreferred)
static KisSurfaceColorimetry::RenderIntent calculateConfigIntent(const KisDisplayConfig::Options &options)
void setDisplayConfigOptions(const KisConfig::CanvasSurfaceMode surfaceMode, const KisDisplayConfig::Options &options)
std::optional< Luminance > luminance
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