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