Krita Source Code Documentation
Loading...
Searching...
No Matches
KoColorSpaceRegistry.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2003 Patrick Julien <freak@codepimps.org>
3 * SPDX-FileCopyrightText: 2004, 2010 Cyrille Berger <cberger@cberger.net>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6*/
7
9
10#include <QHash>
11
12#include <QReadWriteLock>
13#include <QDir>
14#include <QGlobalStatic>
15#include <QColorSpace>
16
17#include "KoPluginLoader.h"
18#include "KoGenericRegistry.h"
19#include "DebugPigment.h"
21#include "KoColorSpace.h"
22#include "KoColorProfile.h"
25#include "KoColorProfileQuery.h"
26
32#include "KoColorSpace_p.h"
33
34#include "kis_assert.h"
37
39
41
42
43struct Q_DECL_HIDDEN KoColorSpaceRegistry::Private {
44
45 // interface for KoColorConversionSystem
46 struct ConversionSystemInterface;
47
48
50
52
55 QHash<QString, const KoColorSpace *> csMap;
56 QScopedPointer<ConversionSystemInterface> conversionSystemInterface;
57 KoColorConversionSystem *colorConversionSystem {nullptr};
58 KoColorConversionCache* colorConversionCache {nullptr};
59 const KoColorSpace *rgbU8sRGB {nullptr};
60 const KoColorSpace *lab16sLAB {nullptr};
61 const KoColorSpace *alphaCs {nullptr};
62 const KoColorSpace *alphaU16Cs {nullptr};
63#ifdef HAVE_OPENEXR
64 const KoColorSpace *alphaF16Cs {nullptr};
65#endif
66 const KoColorSpace *alphaF32Cs {nullptr};
67 QReadWriteLock registrylock;
68
78 const KoColorSpace* getCachedColorSpaceImpl(const QString & csId, const QString & profileName) const;
79
80 QString idsToCacheName(const QString & csId, const QString & profileName) const;
81 QString defaultProfileForCsIdImpl(const QString &csID);
82 const KoColorProfile * profileForCsIdWithFallbackImpl(const QString &csID, const QString &profileName);
83 QString colorSpaceIdImpl(const QString & colorModelId, const QString & colorDepthId) const;
84
85 const KoColorSpace *lazyCreateColorSpaceImpl(const QString &csID, const KoColorProfile *profile);
86
92 template<class LockPolicy = NormalLockPolicy>
93 const KoColorSpace * colorSpace1(const QString &colorSpaceId, const QString &pName = QString());
94
101 const KoColorSpace * colorSpace1(const QString &colorSpaceId, const KoColorProfile *profile);
102};
103
105{
107 : q(parentRegistry)
108 {
109 }
110
111 const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName) override {
112 return q->d->colorSpace1<NoLockPolicy>(q->d->colorSpaceIdImpl(colorModelId, colorDepthId), profileName);
113 }
114
115 const KoColorSpaceFactory* colorSpaceFactory(const QString &colorModelId, const QString &colorDepthId) const override {
116 return q->d->colorSpaceFactoryRegistry.get(q->d->colorSpaceIdImpl(colorModelId, colorDepthId));
117 }
118
120 return q->d->profileStorage.profilesFor(csf);
121 }
122
125 Q_FOREACH (KoColorSpaceFactory* csf, q->d->colorSpaceFactoryRegistry.values()) {
126 if (csf->profileIsCompatible(profile)) {
127 csfs.push_back(csf);
128 }
129 }
130 return csfs;
131 }
132
133private:
135};
136
138{
139 if (!s_instance.exists()) {
140 s_instance->init();
141 }
142 return s_instance;
143}
144
145
147{
148 d->rgbU8sRGB = 0;
149 d->lab16sLAB = 0;
150 d->alphaCs = 0;
151 d->alphaU16Cs = 0;
152#ifdef HAVE_OPENEXR
153 d->alphaF16Cs = 0;
154#endif
155 d->alphaF32Cs = 0;
156
157 d->conversionSystemInterface.reset(new Private::ConversionSystemInterface(this));
158 d->colorConversionSystem = new KoColorConversionSystem(d->conversionSystemInterface.data());
159 d->colorConversionCache = new KoColorConversionCache;
160
162
164
165 // Create the built-in colorspaces
166 QList<KoColorSpaceFactory *> localFactories;
167 localFactories
170 #ifdef HAVE_OPENEXR
171 << new KoAlphaF16ColorSpaceFactory()
172 #endif
177
178 Q_FOREACH (KoColorSpaceFactory *factory, localFactories) {
179 add(factory);
180 }
181
183 config.blacklist = "ColorSpacePluginsDisabled";
184 config.group = "krita";
185 KoPluginLoader::instance()->load("Krita/ColorSpace", config);
186
187 KoPluginLoader::PluginsConfig configExtensions;
188 configExtensions.blacklist = "ColorSpaceExtensionsPluginsDisabled";
189 configExtensions.group = "krita";
190 KoPluginLoader::instance()->load("Krita/ColorSpaceExtension", configExtensions);
191
192
193 dbgPigment << "Loaded the following colorspaces:";
194 Q_FOREACH (const KoID& id, listKeys()) {
195 dbgPigment << "\t" << id.id() << "," << id.name();
196 }
197}
198
200{
201 d->colorConversionSystem = nullptr;
202 d->colorConversionCache = nullptr;
203}
204
206{
207 delete d->colorConversionSystem;
208 d->colorConversionSystem = nullptr;
209
210 Q_FOREACH (const KoColorSpace * cs, d->csMap) {
211 cs->d->deletability = OwnedByRegistryRegistryDeletes;
212 delete cs;
213 }
214 d->csMap.clear();
215
216 // deleting colorspaces calls a function in the cache
217 delete d->colorConversionCache;
218 d->colorConversionCache = nullptr;
219
220 // Delete the colorspace factories
221 Q_FOREACH(KoColorSpaceFactory *f, d->colorSpaceFactoryRegistry.values()) {
222 d->colorSpaceFactoryRegistry.remove(f->id());
223 delete f;
224 }
225 Q_FOREACH(KoColorSpaceFactory *f, d->colorSpaceFactoryRegistry.doubleEntries()) {
226 delete f;
227 }
228
229 delete d;
230}
231
233{
234 {
235 QReadLocker l(&d->registrylock);
236 auto connectionProfileRequests = d->colorConversionSystem->requiredConnectionProfilesFor(item);
237
238 // the actual connection profiles should be added without any lock held
239 l.unlock();
240
241 for (auto it = connectionProfileRequests.begin(); it != connectionProfileRequests.end(); ++it) {
242 const KoColorProfile *profile = this->profileFor(*it, true); // auto-generate all the required profiles
244 }
245 }
246
247 QWriteLocker l(&d->registrylock);
248 d->colorSpaceFactoryRegistry.add(item);
249 d->colorConversionSystem->insertColorSpace(item);
250}
251
253{
254 QWriteLocker l(&d->registrylock);
255
256 QList<QString> toremove;
257 Q_FOREACH (const KoColorSpace * cs, d->csMap) {
258 if (cs->id() == item->id()) {
259 toremove.push_back(d->idsToCacheName(cs->id(), cs->profile()->name()));
260 cs->d->deletability = OwnedByRegistryRegistryDeletes;
261 }
262 }
263
264 Q_FOREACH (const QString& id, toremove) {
265 d->csMap.remove(id);
266 // TODO: should not it delete the color space when removing it from the map ?
267 }
268 d->colorSpaceFactoryRegistry.remove(item->id());
269}
270
271void KoColorSpaceRegistry::addProfileAlias(const QString& name, const QString& to)
272{
273 d->profileStorage.addProfileAlias(name, to);
274}
275
277{
278 d->profileStorage.removeProfileAlias(name);
279}
280
281QString KoColorSpaceRegistry::profileAlias(const QString& name) const
282{
283 return d->profileStorage.profileAlias(name);
284}
285
287{
288 return d->profileStorage.profileByName(name);
289}
290
292{
293 return d->profileStorage.profileByUniqueId(id);
294}
295
297{
298 QReadLocker l(&d->registrylock);
299 return d->profileStorage.profilesFor(d->colorSpaceFactoryRegistry.value(csID));
300}
301
302const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId, const KoColorProfile *profile)
303{
304 return d->colorSpace1(colorSpaceId(colorModelId, colorDepthId), profile);
305}
306
307const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName)
308{
309 return d->colorSpace1(colorSpaceId(colorModelId, colorDepthId), profileName);
310}
311
312const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId)
313{
314 return d->colorSpace1(colorSpaceId(colorModelId, colorDepthId));
315}
316
317bool KoColorSpaceRegistry::profileIsCompatible(const KoColorProfile *profile, const QString &colorSpaceId)
318{
319 QReadLocker l(&d->registrylock);
320 KoColorSpaceFactory *csf = d->colorSpaceFactoryRegistry.value(colorSpaceId);
321
322 return csf ? csf->profileIsCompatible(profile) : false;
323}
324
326{
327 d->profileStorage.addProfile(p);
328}
329
331{
332 if (!p->valid()) return;
333
334 {
335 QReadLocker l(&d->registrylock);
336 auto connectionProfileRequests = d->colorConversionSystem->requiredConnectionProfilesFor(p);
337
338 // the actual connection profiles should be added without any lock held
339 l.unlock();
340
341 for (auto it = connectionProfileRequests.begin(); it != connectionProfileRequests.end(); ++it) {
342 const KoColorProfile *profile = this->profileFor(*it, true); // auto-generate all the required profiles
344 }
345
346 }
347
348 QWriteLocker locker(&d->registrylock);
349 if (p->valid()) {
351 d->colorConversionSystem->insertColorProfile(p);
352 }
353}
354
356{
357 addProfile(profile->clone());
358}
359
361{
362 d->profileStorage.removeProfile(profile);
363 // FIXME: how about removing it from conversion system?
364}
365
366const KoColorSpace* KoColorSpaceRegistry::Private::getCachedColorSpaceImpl(const QString & csID, const QString & profileName) const
367{
368 auto it = csMap.find(idsToCacheName(csID, profileName));
369
370 if (it != csMap.end()) {
371 return it.value();
372 }
373
374 return 0;
375}
376
377QString KoColorSpaceRegistry::Private::idsToCacheName(const QString & csID, const QString & profileName) const
378{
379 return csID + "<comb>" + profileName;
380}
381
382QString KoColorSpaceRegistry::defaultProfileForColorSpace(const QString &colorSpaceId) const
383{
384 QReadLocker l(&d->registrylock);
385 return d->defaultProfileForCsIdImpl(colorSpaceId);
386}
387
388KoColorConversionTransformation *KoColorSpaceRegistry::createColorConverter(const KoColorSpace *srcColorSpace, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
389{
390 QWriteLocker l(&d->registrylock);
391 return d->colorConversionSystem->createColorConverter(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags);
392}
393
394void KoColorSpaceRegistry::createColorConverters(const KoColorSpace *colorSpace, const QList<QPair<KoID, KoID> > &possibilities, KoColorConversionTransformation *&fromCS, KoColorConversionTransformation *&toCS) const
395{
396 QWriteLocker l(&d->registrylock);
397 d->colorConversionSystem->createColorConverters(colorSpace, possibilities, fromCS, toCS);
398}
399
400QString KoColorSpaceRegistry::Private::defaultProfileForCsIdImpl(const QString &csID)
401{
402 QString defaultProfileName;
403
404 KoColorSpaceFactory *csf = colorSpaceFactoryRegistry.value(csID);
405 if (csf) {
406 defaultProfileName = csf->defaultProfile();
407 } else {
408 dbgPigmentCSRegistry << "Unknown color space type : " << csID;
409 }
410
411 return defaultProfileName;
412}
413
414const KoColorProfile *KoColorSpaceRegistry::Private::profileForCsIdWithFallbackImpl(const QString &csID, const QString &profileName)
415{
416 const KoColorProfile *profile = 0;
417
418 // last attempt at getting a profile, sometimes the default profile, like adobe cmyk isn't available.
419 profile = profileStorage.profileByName(profileName);
420 if (!profile) {
421 dbgPigmentCSRegistry << "Profile not found :" << profileName;
422
423 // first try: default
424 profile = profileStorage.profileByName(defaultProfileForCsIdImpl(csID));
425
426 if (!profile) {
427 // second try: first profile in the list
428 QList<const KoColorProfile *> profiles = profileStorage.profilesFor(colorSpaceFactoryRegistry.value(csID));
429 if (profiles.isEmpty() || !profiles.first()) {
430 dbgPigmentCSRegistry << "Couldn't fetch a fallback profile:" << profileName;
431 qWarning() << "profileForCsIdWithFallbackImpl couldn't fetch a fallback profile for " << qUtf8Printable(profileName);
432 return 0;
433 }
434
435 profile = profiles.first();
436 }
437 }
438
439 return profile;
440}
441
442const KoColorSpace *KoColorSpaceRegistry::Private::lazyCreateColorSpaceImpl(const QString &csID, const KoColorProfile *profile)
443{
444 const KoColorSpace *cs = 0;
445
446 /*
447 * We need to check again here, a thread requesting the same colorspace could've added it
448 * already, in between the read unlock and write lock.
449 * TODO: We also potentially changed profileName content, which means we maybe are going to
450 * create a colorspace that's actually in the space registry cache, but currently this might
451 * not be an issue because the colorspace should be cached also by the factory, so it won't
452 * create a new instance. That being said, having two caches with the same stuff doesn't make
453 * much sense.
454 */
455 cs = getCachedColorSpaceImpl(csID, profile->name());
456 if (!cs) {
457 KoColorSpaceFactory *csf = colorSpaceFactoryRegistry.value(csID);
458 if (!csf) {
459 qWarning() << "Unable to create color space factory for" << csID;
460 return 0;
461 }
462 cs = csf->grabColorSpace(profile);
463 if (!cs) {
464 dbgPigmentCSRegistry << "Unable to create color space";
465 qWarning() << "lazyCreateColorSpaceImpl was unable to create a color space for " << csID;
466 return 0;
467 }
468
469 dbgPigmentCSRegistry << "colorspace count: " << csMap.count()
470 << ", adding name: " << idsToCacheName(cs->id(), cs->profile()->name())
471 << "\n\tcsID" << csID
472 << "\n\tcs->id()" << cs->id()
473 << "\n\tcs->profile()->name()" << cs->profile()->name()
474 << "\n\tprofile->name()" << profile->name();
475 Q_ASSERT(cs->id() == csID);
476 Q_ASSERT(cs->profile()->name() == profile->name());
477 csMap[idsToCacheName(cs->id(), cs->profile()->name())] = cs;
478 cs->d->deletability = OwnedByRegistryDoNotDelete;
479 }
480
481 return cs;
482}
483
484template<class LockPolicy>
485const KoColorSpace * KoColorSpaceRegistry::Private::colorSpace1(const QString &csID, const QString &pName)
486{
487 QString profileName = pName;
488
489 const KoColorSpace *cs = 0;
490
491 {
492 typename LockPolicy::ReadLocker l(&registrylock);
493
494 if (profileName.isEmpty()) {
495 profileName = defaultProfileForCsIdImpl(csID);
496 }
497
498 if (!profileName.isEmpty()) {
499 // quick attempt to fetch a cached color space
500 cs = getCachedColorSpaceImpl(csID, profileName);
501 }
502 }
503
504 if (!cs) {
505 // slow attempt to create a color space
506 typename LockPolicy::WriteLocker l(&registrylock);
507
508 const KoColorProfile *profile =
509 profileForCsIdWithFallbackImpl(csID, profileName);
510
511 if (!profile) return 0;
512
513 cs = lazyCreateColorSpaceImpl(csID, profile);
514 }
515 else {
516 KIS_SAFE_ASSERT_RECOVER_NOOP(cs->id() == csID);
517 KIS_SAFE_ASSERT_RECOVER_NOOP(cs->profile()->name() == profileName);
518 }
519
520 return cs;
521}
522
523
524const KoColorSpace * KoColorSpaceRegistry::Private::colorSpace1(const QString &csID, const KoColorProfile *profile)
525{
526 if (csID.isEmpty()) {
527 return 0;
528 } else if (!profile) {
529 return colorSpace1(csID);
530 }
531
532 const KoColorSpace *cs = 0;
533
534 {
535 QReadLocker l(&registrylock);
536 cs = getCachedColorSpaceImpl(csID, profile->name());
537 }
538
539 // the profile should have already been added to the registry by createColorProfile() method
540 KIS_SAFE_ASSERT_RECOVER(profileStorage.containsProfile(profile)) {
541 // warning! locking happens inside addProfile!
542 q->addProfile(profile);
543 }
544
545 if (!cs) {
546 // The profile was not stored and thus not the combination either
547 QWriteLocker l(&registrylock);
548 KoColorSpaceFactory *csf = colorSpaceFactoryRegistry.value(csID);
549
550 if (!csf) {
551 dbgPigmentCSRegistry << "Unknown color space type :" << csf;
552 return 0;
553 }
554
555 if (!csf->profileIsCompatible(profile)) {
556 dbgPigmentCSRegistry << "Profile is not compatible:" << csf << profile->name();
557 return 0;
558 }
559
560 cs = lazyCreateColorSpaceImpl(csID, profile);
561 }
562
563 return cs;
564}
565
567{
568 if (!d->alphaCs) {
569 d->alphaCs = d->colorSpace1(KoAlphaColorSpace::colorSpaceId());
570 }
571 Q_ASSERT(d->alphaCs);
572 return d->alphaCs;
573}
574
576{
577 if (!d->alphaU16Cs) {
578 d->alphaU16Cs = d->colorSpace1(KoAlphaU16ColorSpace::colorSpaceId());
579 }
580 Q_ASSERT(d->alphaU16Cs);
581 return d->alphaU16Cs;
582}
583
584#ifdef HAVE_OPENEXR
585const KoColorSpace * KoColorSpaceRegistry::alpha16f()
586{
587 if (!d->alphaF16Cs) {
588 d->alphaF16Cs = d->colorSpace1(KoAlphaF16ColorSpace::colorSpaceId());
589 }
590 Q_ASSERT(d->alphaF16Cs);
591 return d->alphaF16Cs;
592}
593#endif
594
596{
597 if (!d->alphaF32Cs) {
598 d->alphaF32Cs = d->colorSpace1(KoAlphaF32ColorSpace::colorSpaceId());
599 }
600 Q_ASSERT(d->alphaF32Cs);
601 return d->alphaF32Cs;
602}
603
604const KoColorSpace *KoColorSpaceRegistry::graya8(const QString &profile)
605{
606
607 if (profile.isEmpty()) {
608 KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(GrayAColorModelID.id());
610 }
611 else {
613 }
614
615}
616
618{
619 if (!profile) {
620 return graya8();
621 }
622 else {
624 }
625
626}
627
628const KoColorSpace *KoColorSpaceRegistry::graya16(const QString &profile)
629{
630 if (profile.isEmpty()) {
631 KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(GrayAColorModelID.id());
633 }
634 else {
636 }
637
638}
639
641{
642 if (!profile) {
643 return graya16();
644 }
645 else {
647 }
648}
649
650
651const KoColorSpace * KoColorSpaceRegistry::rgb8(const QString &profileName)
652{
653 if (profileName.isEmpty()) {
654 if (!d->rgbU8sRGB) {
655 d->rgbU8sRGB = d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId());
656 }
657 Q_ASSERT(d->rgbU8sRGB);
658 return d->rgbU8sRGB;
659 }
660 return d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId(), profileName);
661}
662
664{
665 if (profile == 0) {
666 if (!d->rgbU8sRGB) {
667 d->rgbU8sRGB = d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId());
668 }
669 Q_ASSERT(d->rgbU8sRGB);
670 return d->rgbU8sRGB;
671 }
672 return d->colorSpace1(KoRgbU8ColorSpace::colorSpaceId(), profile);
673}
674
675const KoColorSpace * KoColorSpaceRegistry::rgb16(const QString &profileName)
676{
677 return d->colorSpace1(KoRgbU16ColorSpace::colorSpaceId(), profileName);
678}
679
681{
682 return d->colorSpace1(KoRgbU16ColorSpace::colorSpaceId(), profile);
683}
684
685const KoColorSpace * KoColorSpaceRegistry::lab16(const QString &profileName)
686{
687 if (profileName.isEmpty()) {
688 if (!d->lab16sLAB) {
689 d->lab16sLAB = d->colorSpace1(KoLabColorSpace::colorSpaceId());
690 }
691 return d->lab16sLAB;
692 }
693 return d->colorSpace1(KoLabColorSpace::colorSpaceId(), profileName);
694}
695
697{
698 if (profile == 0) {
699 if (!d->lab16sLAB) {
700 d->lab16sLAB = d->colorSpace1(KoLabColorSpace::colorSpaceId());
701 }
702 Q_ASSERT(d->lab16sLAB);
703 return d->lab16sLAB;
704 }
705 return d->colorSpace1(KoLabColorSpace::colorSpaceId(), profile);
706}
707
709{
710 return profileByName("Rec2020-elle-V4-g10.icc");
711}
712
714{
715 return profileByName("Krita Rec. 2100 Perceptual Quantizer (203cd/m²)");
716}
717
719{
720 return profileByName("sRGB-elle-V2-g10.icc");
721}
722
724{
725 return profileByName("sRGB-elle-V2-srgbtrc.icc");
726}
727
728const KoColorProfile *KoColorSpaceRegistry::profileFor(const KoColorProfileQuery &query, const bool generate) const
729{
730 // We're currently disabling on-the-fly generation of PQ profiles, because it can cause
731 // some issues with inconsistent hdr-reference-white value, i.e. the user of this
732 // code may get nits value he/she didn't expect
733 return profileForInternal(query, generate && query.transfer != TRC_ITU_R_BT_2100_0_PQ);
734}
735
737{
738 if (query.primaries == PRIMARIES_ITU_R_BT_709_5) {
739 if (query.transfer == TRC_IEC_61966_2_1) {
740 return p709SRGBProfile();
741 } else if (query.transfer == TRC_LINEAR) {
742 return p709G10Profile();
743 }
744 }
745
748 && query.hdrReferenceWhite
749 && qFuzzyCompare(*query.hdrReferenceWhite, 203.0)) {
750 return p2020PQProfile();
751 } else if (query.transfer == TRC_LINEAR) {
752 return p2020G10Profile();
753 }
754 }
755
756 QList<const KoColorProfile*> list = d->profileStorage.profilesFor(query);
757 if (!list.empty()) {
758 return list.first();
759 }
760
762
763 if (engine && generate) {
764 return engine->getProfile(query);
765 }
766
767 return nullptr;
768}
769
770static QMap<QColorSpace::Primaries, ColorPrimaries> mapQColorSpaceColorPrimaries {
771 {QColorSpace::Primaries::SRgb, PRIMARIES_ITU_R_BT_709_5},
772 {QColorSpace::Primaries::AdobeRgb, PRIMARIES_ADOBE_RGB_1998},
773 {QColorSpace::Primaries::DciP3D65, PRIMARIES_SMPTE_EG_432_1},
774 {QColorSpace::Primaries::ProPhotoRgb, PRIMARIES_PROPHOTO},
775#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
776 {QColorSpace::Primaries::Bt2020, PRIMARIES_ITU_R_BT_2020_2_AND_2100_0},
777#endif
778 {QColorSpace::Primaries::Custom, PRIMARIES_UNSPECIFIED}
779};
780
781static QMap<QColorSpace::TransferFunction, TransferCharacteristics> mapQColorSpaceColorTransfer {
782 {QColorSpace::TransferFunction::Linear, TRC_LINEAR},
783 {QColorSpace::TransferFunction::SRgb, TRC_IEC_61966_2_1},
784 {QColorSpace::TransferFunction::ProPhotoRgb, TRC_PROPHOTO},
785#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
786 {QColorSpace::TransferFunction::Bt2020, TRC_ITU_R_BT_2020_2_12bit},
787 {QColorSpace::TransferFunction::St2084, TRC_ITU_R_BT_2100_0_PQ},
788 {QColorSpace::TransferFunction::Hlg, TRC_ITU_R_BT_2100_0_HLG},
789#endif
790 {QColorSpace::TransferFunction::Custom, TRC_UNSPECIFIED}
791};
792
794{
795 if (!space.isValid()) return p709SRGBProfile();
796 const KoColorProfile *profile = nullptr;
797#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
798 ColorPrimaries primaries = mapQColorSpaceColorPrimaries.value(space.primaries(), PRIMARIES_UNSPECIFIED);
799 TransferCharacteristics transfer = mapQColorSpaceColorTransfer.value(space.transferFunction(), TRC_UNSPECIFIED);
800 if (space.transferFunction() == QColorSpace::TransferFunction::Gamma || (transfer == TRC_UNSPECIFIED && primaries == PRIMARIES_UNSPECIFIED)) {
801 // load icc profile.
802 QByteArray profileData = space.iccProfile();
804 } else {
805 QVector<double> colorants;
806 profile = profileFor(KoColorProfileQuery(primaries, transfer));
807 }
808#else
809 // We want to test RGB before testing wether we are dealing with a LUT, because we want to funnel rec2020 pq into loading our profile.
810
811 if (space.colorModel() == QColorSpace::ColorModel::Rgb) {
812 if (space.transferFunction() == QColorSpace::TransferFunction::Gamma) {
813 // load icc profile.
814 QByteArray profileData = space.iccProfile();
816 }
817 ColorPrimaries primaries = mapQColorSpaceColorPrimaries.value(space.primaries(), PRIMARIES_UNSPECIFIED);
818 TransferCharacteristics transfer = mapQColorSpaceColorTransfer.value(space.transferFunction(), TRC_UNSPECIFIED);
819 // In qt6.9 we can get the 'primary points'.
820 if (!profile && (primaries != PRIMARIES_UNSPECIFIED) && transfer != TRC_UNSPECIFIED) {
821 profile = profileFor(KoColorProfileQuery(primaries, transfer));
822 }
823 } else if (space.colorModel() == QColorSpace::ColorModel::Gray) {
824 TransferCharacteristics transfer = mapQColorSpaceColorTransfer.value(space.transferFunction(), TRC_UNSPECIFIED);
825 if (space.transferFunction() == QColorSpace::TransferFunction::Gamma) {
826 QByteArray profileData = space.iccProfile();
828
829 }
830 QVector<double> colorants;
832 query.whitePoint = {space.whitePoint().x(), space.whitePoint().y()};
833 if (!profile && query.isValid()) {
834 profile = profileFor(query);
835 }
836 }
837#endif
838 if (!profile) {
839 // We might now have a non-qt supported colormodel. Let's load the raw data directly.
841 if (engine) {
842 QByteArray profileData = space.iccProfile();
843 profile = engine->addProfile(profileData);
844 }
845 }
846 if (profile) {
847 const KoColorProfile *preExisting = profileByUniqueId(profile->uniqueId());
848 if (!preExisting) {
849 preExisting = profileByName(profile->name());
850 }
851 if (preExisting) {
852 return preExisting;
853 } else {
854 addProfile(profile);
855 }
856 }
857
858 return profile;
859}
860
862{
863 if (!profile) return QColorSpace(QColorSpace::SRgb);
864 if (profile == p709SRGBProfile()) {
865 return QColorSpace(QColorSpace::SRgb);
866 }
867#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
868 if (profile == p2020PQProfile()
871 return QColorSpace(QColorSpace::Bt2100Pq);
872 }
873 // TODO: we'll need to fine tune the conversion between non "rec 2100" pq profiles and qcolorspace.
874 // Though, arguably, these probably don't show up in a QColorSpace way, and should roundtrip in the icc cicp values.
875#endif
876 return QColorSpace::fromIccProfile(profile->rawData());
877}
878
880{
881 QReadLocker l(&d->registrylock);
882
883 QList<KoID> ids;
884 QList<KoColorSpaceFactory*> factories = d->colorSpaceFactoryRegistry.values();
885 Q_FOREACH (KoColorSpaceFactory* factory, factories) {
886 if (!ids.contains(factory->colorModelId())
887 && (option == AllColorSpaces || factory->userVisible())) {
888 ids << factory->colorModelId();
889 }
890 }
891 return ids;
892}
894{
895 return colorDepthList(colorModelId.id(), option);
896}
897
898
900{
901 QReadLocker l(&d->registrylock);
902
903 QList<KoID> ids;
904 QList<KoColorSpaceFactory*> factories = d->colorSpaceFactoryRegistry.values();
905 Q_FOREACH (KoColorSpaceFactory* factory, factories) {
906 if (!ids.contains(KoID(factory->colorDepthId()))
907 && factory->colorModelId().id() == colorModelId
908 && (option == AllColorSpaces || factory->userVisible())) {
909 ids << factory->colorDepthId();
910 }
911 }
912 QList<KoID> r;
913
916 if (ids.contains(Float16BitsColorDepthID)) r << Float16BitsColorDepthID;
917 if (ids.contains(Float32BitsColorDepthID)) r << Float32BitsColorDepthID;
918 if (ids.contains(Float64BitsColorDepthID)) r << Float64BitsColorDepthID;
919
920 return r;
921}
922
923QString KoColorSpaceRegistry::Private::colorSpaceIdImpl(const QString & colorModelId, const QString & colorDepthId) const
924{
925 for (auto it = colorSpaceFactoryRegistry.constBegin(); it != colorSpaceFactoryRegistry.constEnd(); ++it) {
926 if (it.value()->colorModelId().id() == colorModelId && it.value()->colorDepthId().id() == colorDepthId) {
927 return it.value()->id();
928 }
929 }
930 return "";
931}
932
933QString KoColorSpaceRegistry::colorSpaceId(const QString & colorModelId, const QString & colorDepthId) const
934{
935 QReadLocker l(&d->registrylock);
936 return d->colorSpaceIdImpl(colorModelId, colorDepthId);
937}
938
939QString KoColorSpaceRegistry::colorSpaceId(const KoID& colorModelId, const KoID& colorDepthId) const
940{
941 return colorSpaceId(colorModelId.id(), colorDepthId.id());
942}
943
944KoID KoColorSpaceRegistry::colorSpaceColorModelId(const QString & _colorSpaceId) const
945{
946 QReadLocker l(&d->registrylock);
947
948 KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(_colorSpaceId);
949 if (factory) {
950 return factory->colorModelId();
951 } else {
952 return KoID();
953 }
954}
955
956KoID KoColorSpaceRegistry::colorSpaceColorDepthId(const QString & _colorSpaceId) const
957{
958 QReadLocker l(&d->registrylock);
959
960 KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(_colorSpaceId);
961 if (factory) {
962 return factory->colorDepthId();
963 } else {
964 return KoID();
965 }
966}
967
969{
970 return d->colorConversionSystem;
971}
972
974{
975 return d->colorConversionCache;
976}
977
979{
980 if (_colorSpace->d->deletability != NotOwnedByRegistry) {
981 return _colorSpace;
982 } else if (*_colorSpace == *d->alphaCs) {
983 return d->alphaCs;
984 } else {
985 const KoColorSpace* cs = d->colorSpace1(_colorSpace->id(), _colorSpace->profile());
986 Q_ASSERT(cs);
987 Q_ASSERT(*cs == *_colorSpace);
988 return cs;
989 }
990}
991
993{
994 QReadLocker l(&d->registrylock);
995 QList<KoID> answer;
996 Q_FOREACH (const QString& key, d->colorSpaceFactoryRegistry.keys()) {
997 answer.append(KoID(key, d->colorSpaceFactoryRegistry.get(key)->name()));
998 }
999
1000 return answer;
1001}
1002
1003const KoColorProfile* KoColorSpaceRegistry::createColorProfile(const QString& colorModelId, const QString& colorDepthId, const QByteArray& rawData)
1004{
1005 return createColorProfile(colorModelId, colorDepthId, rawData, {});
1006}
1007
1008const KoColorProfile* KoColorSpaceRegistry::createColorProfile(const QString & colorModelId, const QString & colorDepthId, const QByteArray& rawData, CustomProfileNameAlias customProfileNameAlias)
1009{
1010 QReadLocker l(&d->registrylock);
1011
1012 KoColorSpaceFactory* factory_ = d->colorSpaceFactoryRegistry.get(d->colorSpaceIdImpl(colorModelId, colorDepthId));
1013 std::unique_ptr<KoColorProfile> profile(factory_->createColorProfile(rawData));
1014
1015 l.unlock();
1016
1017 if (profile && profile->valid()) {
1018 const QString effectiveProfileName = customProfileNameAlias.value(profile->name(), profile->name());
1019 if (const KoColorProfile* existingProfile = profileByName(effectiveProfileName)) {
1020 return existingProfile;
1021 }
1022
1023 const KoColorProfile *constProfile = profile.get();
1024 this->addProfile(profile.release());
1025
1026 return constProfile;
1027 }
1028
1029 return nullptr;
1030}
1031
1033{
1034 QList<const KoColorSpace*> colorSpaces;
1035
1036 // TODO: thread-unsafe code: the factories might change right after the lock in released
1037 // HINT: used in a unittest only!
1038
1039 d->registrylock.lockForRead();
1040 QList<KoColorSpaceFactory*> factories = d->colorSpaceFactoryRegistry.values();
1041 d->registrylock.unlock();
1042
1043 Q_FOREACH (KoColorSpaceFactory* factory, factories) {
1044 // Don't test with ycbcr for now, since we don't have a default profile for it.
1045 if (factory->colorModelId().id().startsWith("Y")) continue;
1046 if (visibility == AllColorSpaces || factory->userVisible()) {
1047 if (pSelection == OnlyDefaultProfile) {
1048 const KoColorSpace *cs = d->colorSpace1(factory->id());
1049 if (cs) {
1050 colorSpaces.append(cs);
1051 }
1052 else {
1053 warnPigment << "Could not create colorspace for id" << factory->id() << "since there is no working default profile";
1054 }
1055 } else {
1057 Q_FOREACH (const KoColorProfile * profile, profiles) {
1058 const KoColorSpace *cs = d->colorSpace1(factory->id(), profile);
1059 if (cs) {
1060 colorSpaces.append(cs);
1061 }
1062 else {
1063 warnPigment << "Could not create colorspace for id" << factory->id() << "and profile" << profile->name();
1064 }
1065 }
1066 }
1067 }
1068 }
1069
1070 return colorSpaces;
1071}
#define dbgPigment
#define warnPigment
#define dbgPigmentCSRegistry
const Params2D p
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
KoAlphaColorSpaceFactoryImpl< AlphaU16Traits > KoAlphaU16ColorSpaceFactory
KoAlphaColorSpaceFactoryImpl< AlphaU8Traits > KoAlphaColorSpaceFactory
KoAlphaColorSpaceFactoryImpl< AlphaF32Traits > KoAlphaF32ColorSpaceFactory
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID Float64BitsColorDepthID("F64", ki18n("64-bit float/channel"))
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID Float16BitsColorDepthID("F16", ki18n("16-bit float/channel"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
ColorPrimaries
The colorPrimaries enum Enum of colorants, follows ITU H.273 for values 0 to 255, and has extra known...
@ PRIMARIES_ITU_R_BT_2020_2_AND_2100_0
@ PRIMARIES_UNSPECIFIED
@ PRIMARIES_PROPHOTO
@ PRIMARIES_ADOBE_RGB_1998
@ PRIMARIES_SMPTE_EG_432_1
@ PRIMARIES_ITU_R_BT_709_5
TransferCharacteristics
The transferCharacteristics enum Enum of transfer characteristics, follows ITU H.273 for values 0 to ...
@ TRC_ITU_R_BT_2100_0_HLG
@ TRC_ITU_R_BT_2100_0_PQ
@ TRC_IEC_61966_2_1
@ TRC_ITU_R_BT_2020_2_12bit
static QMap< QColorSpace::Primaries, ColorPrimaries > mapQColorSpaceColorPrimaries
static QMap< QColorSpace::TransferFunction, TransferCharacteristics > mapQColorSpaceColorTransfer
@ NotOwnedByRegistry
@ OwnedByRegistryDoNotDelete
@ OwnedByRegistryRegistryDeletes
static QString colorSpaceId()
The KoColorProfileStorage class is a "composite subclass" of KoColorSpaceRegistry that ensures that t...
const KoColorProfile * profileByName(const QString &name) const
QList< const KoColorProfile * > profilesFor(const KoColorSpaceFactory *csf) const
bool containsProfile(const KoColorProfile *profile)
containsProfile shows if a profile is registered in the storage
static KoColorSpaceEngineRegistry * instance()
Private *const d
virtual const KoColorProfile * profile() const =0
const T value(const QString &id) const
T get(const QString &id) const
QHash< QString, T >::const_iterator constBegin() const
QHash< QString, T >::const_iterator constEnd() const
Definition KoID.h:30
QString id() const
Definition KoID.cpp:63
static QString colorSpaceId()
void load(const QString &serviceType, const PluginsConfig &config=PluginsConfig(), QObject *owner=0, bool cache=true)
static KoPluginLoader * instance()
static QString colorSpaceId()
static QString colorSpaceId()
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
The KoColorProfileQuery struct.
TransferCharacteristics transfer
CICP-compatible enum value representing the white point and primaries.
ColorPrimaries primaries
Rgb Primaries of the profile. When empty, this is a query for a greyscale profile.
bool isValid() const
Used for PQ profiles.
KoColorimetryUtils::xy whitePoint
std::optional< double > hdrReferenceWhite
CICP-compatible enum value representing the transfer function.
virtual QByteArray rawData() const
virtual ColorPrimaries getColorPrimaries() const
getColorPrimaries
virtual QByteArray uniqueId() const =0
virtual KoColorProfile * clone() const =0
virtual TransferCharacteristics getTransferCharacteristics() const
getTransferCharacteristics This function should be subclassed at some point so we can get the value f...
virtual const KoColorProfile * getProfile(const KoColorProfileQuery &query)=0
getProfile This tries to generate a profile with the given characteristics and add it to the registry...
virtual const KoColorProfile * addProfile(const QString &filename)=0
virtual KoID colorDepthId() const =0
virtual QString defaultProfile() const =0
virtual KoID colorModelId() const =0
virtual QString id() const =0
virtual KoColorProfile * createColorProfile(const QByteArray &rawData) const =0
virtual bool userVisible() const =0
virtual bool profileIsCompatible(const KoColorProfile *profile) const =0
const KoColorSpace * grabColorSpace(const KoColorProfile *profile)
QList< const KoColorProfile * > profilesFor(const KoColorSpaceFactory *csf) const override
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const QString &profileName) override
const KoColorSpaceFactory * colorSpaceFactory(const QString &colorModelId, const QString &colorDepthId) const override
QList< const KoColorSpaceFactory * > colorSpacesFor(const KoColorProfile *profile) const override
ConversionSystemInterface(KoColorSpaceRegistry *parentRegistry)
const KoColorProfile * profileByName(const QString &name) const
const KoColorSpace * colorSpace1(const QString &colorSpaceId, const QString &pName=QString())
QString colorSpaceId(const QString &colorModelId, const QString &colorDepthId) const
const KoColorSpace * lab16(const QString &profileName=QString())
const KoColorSpace * alpha32f()
KoColorConversionCache * colorConversionCache
const KoColorSpace * lazyCreateColorSpaceImpl(const QString &csID, const KoColorProfile *profile)
QList< KoID > colorDepthList(const KoID &colorModelId, ColorSpaceListVisibility option) const
void addProfileAlias(const QString &name, const QString &to)
const KoColorSpace * permanentColorspace(const KoColorSpace *_colorSpace)
@ OnlyDefaultProfile
Only add the default profile.
QString idsToCacheName(const QString &csId, const QString &profileName) const
QString colorSpaceIdImpl(const QString &colorModelId, const QString &colorDepthId) const
QString defaultProfileForCsIdImpl(const QString &csID)
QList< const KoColorSpace * > allColorSpaces(ColorSpaceListVisibility visibility, ColorSpaceListProfilesSelection pSelection)
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
const KoColorSpace * colorSpace1(const QString &colorSpaceId, const KoColorProfile *profile)
KoColorConversionTransformation * createColorConverter(const KoColorSpace *srcColorSpace, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
static KoColorSpaceRegistry * instance()
Private(KoColorSpaceRegistry *_q)
const KoColorSpace * graya8(const QString &profile=QString())
QHash< QString, QString > CustomProfileNameAlias
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 KoColorSpace * graya16(const QString &profile=QString())
KoID colorSpaceColorDepthId(const QString &_colorSpaceId) const
const KoColorProfile * p709G10Profile() const
const KoColorProfile * p709SRGBProfile() const
void remove(KoColorSpaceFactory *item)
KoGenericRegistry< KoColorSpaceFactory * > colorSpaceFactoryRegistry
const KoColorProfile * profileForInternal(const KoColorProfileQuery &query, const bool generate) const
const KoColorSpace * rgb8(const QString &profileName=QString())
const KoColorProfile * profileForQColorSpace(const QColorSpace &space)
profileForQColorSpace Find a KoColorProfile that matches a given QColorSpace. This will use the QColo...
bool profileIsCompatible(const KoColorProfile *profile, const QString &colorSpaceId)
QString defaultProfileForColorSpace(const QString &colorSpaceId) const
void removeProfileAlias(const QString &name)
const KoColorProfile * profileByUniqueId(const QByteArray &id) const
const KoColorProfile * p2020PQProfile() const
const KoColorSpace * getCachedColorSpaceImpl(const QString &csId, const QString &profileName) const
const KoColorSpace * rgb16(const QString &profileName=QString())
const KoColorSpace * alpha8()
const KoColorProfile * profileForCsIdWithFallbackImpl(const QString &csID, const QString &profileName)
QList< KoID > colorModelsList(ColorSpaceListVisibility option) const
KoID colorSpaceColorModelId(const QString &_colorSpaceId) const
void add(KoColorSpaceFactory *item)
QList< const KoColorProfile * > profilesFor(const QString &csID) const
QColorSpace QColorSpaceForProfile(const KoColorProfile *profile) const
QColorSpaceForProfile Generate a QColorSpace for the given KoColorProfile. Will return sRGB when the ...
void addProfileToMap(KoColorProfile *p)
QString profileAlias(const QString &name) const
const KoColorSpace * alpha16()
void createColorConverters(const KoColorSpace *colorSpace, const QList< QPair< KoID, KoID > > &possibilities, KoColorConversionTransformation *&fromCS, KoColorConversionTransformation *&toCS) const
const KoColorProfile * p2020G10Profile() const
KoColorConversionSystem * colorConversionSystem
void removeProfile(KoColorProfile *profile)
@ AllColorSpaces
All color space even those not visible to the user.
const KoColorProfile * createColorProfile(const QString &colorModelId, const QString &colorDepthId, const QByteArray &rawData)
QScopedPointer< ConversionSystemInterface > conversionSystemInterface
void addProfile(KoColorProfile *profile)
QHash< QString, const KoColorSpace * > csMap
KoColorProfileStorage profileStorage
QList< KoID > listKeys() const
const char * blacklist
This contains the variable name for the list of plugins (by library name) that will not be loaded.