[graphics/krita] /: Fix generation of Display P3 PQ profile

Wolthera van Hövell <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 347ce8bc050f19ffefe440d24c1f1df3805ccb0c by Wolthera van Hövell, on behalf of Dmitry Kazakov.
Committed on 16/07/2026 at 16:26.
Pushed by woltherav into branch 'master'.

Fix generation of Display P3 PQ profile

The patch implements a concept of "connection
profiles" for KoColorSpaceFactories. This avoids
recursion (and hence a deadlock) when trying
to connect a PQ profile to its corresponding
linear profile in Rgb*ColorSpaceFactory::
colorConversionLinksFromProfile().

The patch also adds a unittests for verifying that.

M  +42   -0    libs/pigment/KoColorConversionSystem.cpp
M  +59   -4    libs/pigment/KoColorConversionSystem.h
M  +13   -0    libs/pigment/KoColorProfileQuery.h
M  +7    -2    libs/pigment/KoColorProfileStorage.cpp
M  +6    -20   libs/pigment/KoColorSpaceFactory.cpp
M  +4    -14   libs/pigment/KoColorSpaceFactory.h
M  +55   -23   libs/pigment/KoColorSpaceRegistry.cpp
M  +8    -0    libs/pigment/KoColorSpaceRegistry.h
M  +15   -2    plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp
M  +1    -0    plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.h
M  +14   -2    plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp
M  +1    -0    plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.h
M  +15   -2    plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp
M  +1    -0    plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.h
M  +15   -2    plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp
M  +1    -0    plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.h
M  +46   -10   plugins/color/lcms2engine/tests/TestProfileGeneration.cpp

https://invent.kde.org/graphics/krita/-/commit/347ce8bc050f19ffefe440d24c1f1df3805ccb0c

diff --git a/libs/pigment/KoColorConversionSystem.cpp b/libs/pigment/KoColorConversionSystem.cpp
index 65afed2596e..a4a2b22dbda 100644
--- a/libs/pigment/KoColorConversionSystem.cpp
+++ b/libs/pigment/KoColorConversionSystem.cpp
@@ -13,6 +13,7 @@
 #include "KoColorConversionAlphaTransformation.h"
 #include "KoColorConversionTransformation.h"
 #include "KoColorProfile.h"
+#include "KoColorProfileQuery.h"
 #include "KoColorSpace.h"
 #include "KoCopyColorConversionTransformation.h"
 #include "KoMultipleColorConversionTransformation.h"
@@ -52,6 +53,26 @@ KoColorConversionSystem::Node* KoColorConversionSystem::insertEngine(const KoCol
     return n;
 }
 
+QList<KoColorProfileQuery> KoColorConversionSystem::requiredConnectionProfilesFor(const KoColorSpaceFactory* csf)
+{
+    QList<KoColorProfileQuery> profileQueries;
+
+    const QList<const KoColorProfile*> existingProfiles = d->registryInterface->profilesFor(csf);
+    Q_FOREACH (const KoColorProfile* profile, existingProfiles) {
+        profileQueries.append(csf->requiredConnectionProfiles(profile));
+    }
+
+    // TODO: make a better 'unique' function without sorting
+    QList<KoColorProfileQuery> uniqueProfileQueries;
+    std::copy_if(profileQueries.begin(),
+                 profileQueries.end(),
+                 std::back_inserter(uniqueProfileQueries),
+                 [&](const KoColorProfileQuery &query) {
+                     return !uniqueProfileQueries.contains(query);
+                 });
+
+    return uniqueProfileQueries;
+}
 
 void KoColorConversionSystem::insertColorSpace(const KoColorSpaceFactory* csf)
 {
@@ -115,6 +136,27 @@ void KoColorConversionSystem::insertColorSpace(const KoColorSpaceFactory* csf)
     }
 }
 
+QList<KoColorProfileQuery> KoColorConversionSystem::requiredConnectionProfilesFor(const KoColorProfile* profile)
+{
+    QList<KoColorProfileQuery> profileQueries;
+
+    const QList< const KoColorSpaceFactory* >& factories = d->registryInterface->colorSpacesFor(profile);
+    Q_FOREACH (const KoColorSpaceFactory* factory, factories) {
+        profileQueries.append(factory->requiredConnectionProfiles(profile));
+    }
+
+    // TODO: make a better 'unique' function without sorting
+    QList<KoColorProfileQuery> uniqueProfileQueries;
+    std::copy_if(profileQueries.begin(),
+                 profileQueries.end(),
+                 std::back_inserter(uniqueProfileQueries),
+                 [&](const KoColorProfileQuery &query) {
+                     return !uniqueProfileQueries.contains(query);
+                 });
+
+    return uniqueProfileQueries;
+}
+
 void KoColorConversionSystem::insertColorProfile(const KoColorProfile* _profile)
 {
     dbgPigmentCCS << _profile->name();
diff --git a/libs/pigment/KoColorConversionSystem.h b/libs/pigment/KoColorConversionSystem.h
index a343534526f..bd4609d03eb 100644
--- a/libs/pigment/KoColorConversionSystem.h
+++ b/libs/pigment/KoColorConversionSystem.h
@@ -8,6 +8,7 @@
 #define _KO_COLOR_CONVERSION_SYSTEM_H_
 
 class KoColorProfile;
+class KoColorProfileQuery;
 class KoColorSpace;
 class KoColorSpaceFactory;
 class KoColorSpaceEngine;
@@ -53,13 +54,67 @@ public:
      */
     KoColorConversionSystem(RegistryInterface *registryInterface);
     ~KoColorConversionSystem();
+
+    /**
+     * Add a color space to a graph of transformation. The new node will be
+     * added for each known compatible profile.
+     *
+     * Make sure you call `requiredConnectionProfilesFor(csf)` and add all
+     * the required connection profiles into the registry **before** inserting
+     * the actual color space \p csf. Otherwise the created node will not be
+     * able to connect itself into the graph (due to a missing connection
+     * point).
+     *
+     * \see requiredConnectionProfilesFor()
+     */
+    void insertColorSpace(const KoColorSpaceFactory *csf);
+
+    /**
+     * Add a profile to the graph of transformation. The new node will be added
+     * for each known color space compatible with this profile.
+     *
+     * Make sure you call `requiredConnectionProfilesFor(profile)` and add all
+     * the required connection profiles into the registry **before** inserting
+     * the actual profile \p profile. Otherwise the created node will not be
+     * able to connect itself into the graph (due to a missing connection
+     * point).
+     *
+     * \see requiredConnectionProfilesFor()
+     */
+    void insertColorProfile(const KoColorProfile *profile);
+
+    /**
+     * \return a list of connection profiles required for this color space
+     *
+     * Some color space nodes may require custom profiles to be connected
+     * to the color conversion system. I.e. Display P3 PQ space will require
+     * a Display P3 Linear profile to connect itself to the color conversion
+     * system through.
+     *
+     * This function returns a list of such connection profiles.
+     *
+     * Connection profiles should be requested and added to the registry
+     * **before** adding the color space \p csf into the color conversion system
+     * with insertColorSpace(csf).
+     */
+    QList<KoColorProfileQuery> requiredConnectionProfilesFor(const KoColorSpaceFactory* csf);
+
     /**
-     * This function is called by the KoColorSpaceRegistry to add a new color space
-     * to the graph of transformation.
+     * \return a list of connection profiles required for this profile
+     *
+     * Some color space nodes may require custom profiles to be connected
+     * to the color conversion system. I.e. Display P3 PQ space will require
+     * a Display P3 Linear profile to connect itself to the color conversion
+     * system through.
+     *
+     * This function returns a list of such connection profiles.
+     *
+     * Connection profiles should be requested and added to the registry
+     * **before** adding the profile \p profile into the color conversion system
+     * with insertColorProfile(csf).
      */
-    void insertColorSpace(const KoColorSpaceFactory*);
+    QList<KoColorProfileQuery> requiredConnectionProfilesFor(const KoColorProfile* profile);
 
-    void insertColorProfile(const KoColorProfile*);
     /**
      * This function is called by the color space to create a color conversion
      * between two color space. This function search in the graph of transformations
diff --git a/libs/pigment/KoColorProfileQuery.h b/libs/pigment/KoColorProfileQuery.h
index 36b740e2aef..80acacd61b7 100644
--- a/libs/pigment/KoColorProfileQuery.h
+++ b/libs/pigment/KoColorProfileQuery.h
@@ -46,6 +46,19 @@ struct KoColorProfileQuery {
             && (primaries != PRIMARIES_UNSPECIFIED || !rgbColorants.isEmpty())
             && !(whitePoint == KoColorimetryUtils::xy());
     }
+
+    inline bool operator==(const KoColorProfileQuery &rhs) const {
+        return whitePoint == rhs.whitePoint
+            && rgbColorants == rhs.rgbColorants
+            && primaries == rhs.primaries
+            && transfer == rhs.transfer
+            && hdrReferenceWhite == rhs.hdrReferenceWhite;
+    }
+
+    inline bool operator!=(const KoColorProfileQuery &rhs) const {
+        return !(*this == rhs);
+    }
+
 };
 
 inline QDebug operator<<(QDebug debug, const KoColorProfileQuery &value) {
diff --git a/libs/pigment/KoColorProfileStorage.cpp b/libs/pigment/KoColorProfileStorage.cpp
index d4dabfa4575..14966e76cb9 100644
--- a/libs/pigment/KoColorProfileStorage.cpp
+++ b/libs/pigment/KoColorProfileStorage.cpp
@@ -225,8 +225,13 @@ QList<const KoColorProfile *> KoColorProfileStorage::profilesFor(const KoColorPr
             }
         }
 
-        if (query.hdrReferenceWhite && query.transfer == TRC_ITU_R_BT_2100_0_PQ) {
-            luminanceMatch = (profile->hdrReferenceWhite() && qFuzzyCompare(*profile->hdrReferenceWhite(), *query.hdrReferenceWhite));
+        if (query.transfer == TRC_ITU_R_BT_2100_0_PQ) {
+            // by default we expect HDR profiles to have HDR Reference White point
+            // to set to 203 nits
+            // TODO: should we spit a warning if the reference white is not set
+            //       for a pq space?
+            const qreal requestedHdrReferenceWhite = query.hdrReferenceWhite.value_or(203.0);
+            luminanceMatch = (profile->hdrReferenceWhite() && qFuzzyCompare(*profile->hdrReferenceWhite(), requestedHdrReferenceWhite));
         }
 
         if (transferMatch && colorantMatch && colorantTypeMatch && luminanceMatch) {
diff --git a/libs/pigment/KoColorSpaceFactory.cpp b/libs/pigment/KoColorSpaceFactory.cpp
index 423b220f2a7..63a459cb786 100644
--- a/libs/pigment/KoColorSpaceFactory.cpp
+++ b/libs/pigment/KoColorSpaceFactory.cpp
@@ -12,13 +12,13 @@
 #include <QMutexLocker>
 
 #include "KoColorProfile.h"
+#include "KoColorProfileQuery.h"
 #include "KoColorSpace.h"
 #include "KoColorSpaceRegistry.h"
 
 #include "kis_assert.h"
 
 struct Q_DECL_HIDDEN KoColorSpaceFactory::Private {
-    QList<KoColorProfile*> colorprofiles;
     QHash<QString, KoColorSpace* > availableColorspaces;
     QMutex mutex;
 #ifndef NDEBUG
@@ -45,28 +45,9 @@ KoColorSpaceFactory::~KoColorSpaceFactory()
         errorPigment << it.value();
     }
 #endif
-    Q_FOREACH (KoColorProfile* profile, d->colorprofiles) {
-        KoColorSpaceRegistry::instance()->removeProfile(profile);
-        delete profile;
-    }
     delete d;
 }
 
-const KoColorProfile *KoColorSpaceFactory::colorProfile(const QByteArray& rawData, ProfileRegistrationInterface *registrationInterface, const CustomProfileNameAlias &customProfileNameAlias) const
-{
-    KoColorProfile* colorProfile = createColorProfile(rawData);
-    if (colorProfile && colorProfile->valid()) {
-        const QString effectiveProfileName = customProfileNameAlias.value(colorProfile->name(), colorProfile->name());
-        if (const KoColorProfile* existingProfile = registrationInterface->profileByName(effectiveProfileName)) {
-            delete colorProfile;
-            return existingProfile;
-        }
-        registrationInterface->registerNewProfile(colorProfile);
-        d->colorprofiles.append(colorProfile);
-    }
-    return colorProfile;
-}
-
 const KoColorSpace *KoColorSpaceFactory::grabColorSpace(const KoColorProfile * profile)
 {
     QMutexLocker l(&d->mutex);
@@ -88,3 +69,8 @@ const KoColorSpace *KoColorSpaceFactory::grabColorSpace(const KoColorProfile * p
     return cs;
 }
 
+QList<KoColorProfileQuery> KoColorSpaceFactory::requiredConnectionProfiles(const KoColorProfile *profile) const
+{
+    Q_UNUSED(profile)
+    return {};
+}
diff --git a/libs/pigment/KoColorSpaceFactory.h b/libs/pigment/KoColorSpaceFactory.h
index 84789c02fa8..9394d4c7794 100644
--- a/libs/pigment/KoColorSpaceFactory.h
+++ b/libs/pigment/KoColorSpaceFactory.h
@@ -13,6 +13,7 @@
 #include "kritapigment_export.h"
 
 class KoColorProfile;
+class KoColorProfileQuery;
 class KoColorConversionTransformationFactory;
 
 /**
@@ -98,22 +99,10 @@ public:
      */
     virtual QString defaultProfile() const = 0;
 
-    struct ProfileRegistrationInterface
-    {
-        virtual ~ProfileRegistrationInterface() {}
-        virtual const KoColorProfile* profileByName(const QString &profileName) const = 0;
-        virtual void registerNewProfile(KoColorProfile *profile) = 0;
-    };
-
     /**
      * Create a color profile from a memory array, if possible, otherwise return 0.
-     * If there is an existing profile with the same name, it will be used instead,
-     * and the binary data from \p rawData will be ignored
-     *
-     * This will call the descendant's createColorProfile()
      */
-    using CustomProfileNameAlias = QHash<QString, QString>;
-    const KoColorProfile* colorProfile(const QByteArray& rawData, ProfileRegistrationInterface *registrationInterface, const CustomProfileNameAlias &customProfileNameAlias) const;
+    virtual KoColorProfile* createColorProfile(const QByteArray& rawData) const = 0;
 
     /**
      * Create or reuse the existing colorspace for the given profile.
@@ -134,12 +123,13 @@ public:
         return QList<KoColorConversionTransformationFactory*>();
     }
 
+    virtual QList<KoColorProfileQuery> requiredConnectionProfiles(const KoColorProfile *profile) const;
+
 protected:
     /**
      * creates a color space using the given profile.
      */
     virtual KoColorSpace *createColorSpace(const KoColorProfile *) const = 0;
-    virtual KoColorProfile* createColorProfile(const QByteArray& rawData) const = 0;
 private:
     struct Private;
     Private* const d;
diff --git a/libs/pigment/KoColorSpaceRegistry.cpp b/libs/pigment/KoColorSpaceRegistry.cpp
index 5f58930f1be..09093a5c3f7 100644
--- a/libs/pigment/KoColorSpaceRegistry.cpp
+++ b/libs/pigment/KoColorSpaceRegistry.cpp
@@ -42,8 +42,6 @@ Q_GLOBAL_STATIC(KoColorSpaceRegistry, s_instance)
 
 struct Q_DECL_HIDDEN KoColorSpaceRegistry::Private {
 
-    // interface for KoColorSpaceFactory
-    struct ProfileRegistrationInterface;
     // interface for KoColorConversionSystem
     struct ConversionSystemInterface;
 
@@ -233,6 +231,19 @@ KoColorSpaceRegistry::~KoColorSpaceRegistry()
 
 void KoColorSpaceRegistry::add(KoColorSpaceFactory* item)
 {
+    {
+        QReadLocker l(&d->registrylock);
+        auto connectionProfileRequests = d->colorConversionSystem->requiredConnectionProfilesFor(item);
+
+        // the actual connection profiles should be added without any lock held
+        l.unlock();
+
+        for (auto it = connectionProfileRequests.begin(); it != connectionProfileRequests.end(); ++it) {
+            const KoColorProfile *profile = this->profileFor(*it, true); // auto-generate all the required profiles
+            KIS_SAFE_ASSERT_RECOVER_NOOP(profile);
+        }
+    }
+
     QWriteLocker l(&d->registrylock);
     d->colorSpaceFactoryRegistry.add(item);
     d->colorConversionSystem->insertColorSpace(item);
@@ -320,6 +331,20 @@ void KoColorSpaceRegistry::addProfile(KoColorProfile *p)
 {
     if (!p->valid()) return;
 
+    {
+        QReadLocker l(&d->registrylock);
+        auto connectionProfileRequests = d->colorConversionSystem->requiredConnectionProfilesFor(p);
+
+        // the actual connection profiles should be added without any lock held
+        l.unlock();
+
+        for (auto it = connectionProfileRequests.begin(); it != connectionProfileRequests.end(); ++it) {
+            const KoColorProfile *profile = this->profileFor(*it, true); // auto-generate all the required profiles
+            KIS_SAFE_ASSERT_RECOVER_NOOP(profile);
+        }
+
+    }
+
     QWriteLocker locker(&d->registrylock);
     if (p->valid()) {
         addProfileToMap(p);
@@ -701,6 +726,14 @@ const KoColorProfile *KoColorSpaceRegistry::p709SRGBProfile() const
 }
 
 const KoColorProfile *KoColorSpaceRegistry::profileFor(const KoColorProfileQuery &query, const bool generate) const
+{
+    // We're currently disabling on-the-fly generation of PQ profiles, because it can cause
+    // some issues with inconsistent hdr-reference-white value, i.e. the user of this
+    // code may get nits value he/she didn't expect
+    return profileForInternal(query, generate && query.transfer != TRC_ITU_R_BT_2100_0_PQ);
+}
+
+const KoColorProfile *KoColorSpaceRegistry::profileForInternal(const KoColorProfileQuery &query, const bool generate) const
 {
     if (query.primaries == PRIMARIES_ITU_R_BT_709_5) {
         if (query.transfer == TRC_IEC_61966_2_1) {
@@ -726,8 +759,8 @@ const KoColorProfile *KoColorSpaceRegistry::profileFor(const KoColorProfileQuery
     }
 
     KoColorSpaceEngine *engine = KoColorSpaceEngineRegistry::instance()->get("icc");
-    // We're disabling custom generation of PQ profiles for now.
-    if (engine && generate && !(query.hdrReferenceWhite && query.transfer == TRC_ITU_R_BT_2100_0_PQ)) {
+
+    if (engine && generate) {
         return engine->getProfile(query);
     }
 
@@ -967,22 +1000,6 @@ QList<KoID> KoColorSpaceRegistry::listKeys() const
     return answer;
 }
 
-struct KoColorSpaceRegistry::Private::ProfileRegistrationInterface : public KoColorSpaceFactory::ProfileRegistrationInterface
-{
-    ProfileRegistrationInterface(KoColorSpaceRegistry::Private *_d) : d(_d) {}
-
-    const KoColorProfile* profileByName(const QString &profileName) const override {
-        return d->profileStorage.profileByName(profileName);
-    }
-
-    void registerNewProfile(KoColorProfile *profile) override {
-        d->profileStorage.addProfile(profile);
-        d->colorConversionSystem->insertColorProfile(profile);
-    }
-
-    KoColorSpaceRegistry::Private *d {nullptr};
-};
-
 const KoColorProfile* KoColorSpaceRegistry::createColorProfile(const QString& colorModelId, const QString& colorDepthId, const QByteArray& rawData)
 {
     return createColorProfile(colorModelId, colorDepthId, rawData, {});
@@ -990,11 +1007,26 @@ const KoColorProfile* KoColorSpaceRegistry::createColorProfile(const QString& co
 
 const KoColorProfile* KoColorSpaceRegistry::createColorProfile(const QString & colorModelId, const QString & colorDepthId, const QByteArray& rawData, CustomProfileNameAlias customProfileNameAlias)
 {
-    QWriteLocker l(&d->registrylock);
+    QReadLocker l(&d->registrylock);
+
     KoColorSpaceFactory* factory_ = d->colorSpaceFactoryRegistry.get(d->colorSpaceIdImpl(colorModelId, colorDepthId));
+    std::unique_ptr<KoColorProfile> profile(factory_->createColorProfile(rawData));
+
+    l.unlock();
+
+    if (profile && profile->valid()) {
+        const QString effectiveProfileName = customProfileNameAlias.value(profile->name(), profile->name());
+        if (const KoColorProfile* existingProfile = profileByName(effectiveProfileName)) {
+            return existingProfile;
+        }
 
-    Private::ProfileRegistrationInterface interface(d);
-    return factory_->colorProfile(rawData, &interface, customProfileNameAlias);
+        const KoColorProfile *constProfile = profile.get();
+        this->addProfile(profile.release());
+
+        return constProfile;
+    }
+
+    return nullptr;
 }
 
 QList<const KoColorSpace*> KoColorSpaceRegistry::allColorSpaces(ColorSpaceListVisibility visibility, ColorSpaceListProfilesSelection pSelection)
diff --git a/libs/pigment/KoColorSpaceRegistry.h b/libs/pigment/KoColorSpaceRegistry.h
index bcf54837d0d..86341990df0 100644
--- a/libs/pigment/KoColorSpaceRegistry.h
+++ b/libs/pigment/KoColorSpaceRegistry.h
@@ -127,6 +127,11 @@ public:
      */
     const KoColorProfile *createColorProfile(const QString & colorModelId, const QString & colorDepthId, const QByteArray& rawData);
 
+    /**
+     * Create a color profile from a memory array, if possible, otherwise return 0.
+     * If there is an existing profile with the same name, it will be used instead,
+     * and the binary data from \p rawData will be ignored
+     */
     using CustomProfileNameAlias = QHash<QString, QString>;
     const KoColorProfile *createColorProfile(const QString & colorModelId, const QString & colorDepthId, const QByteArray& rawData, CustomProfileNameAlias customProfileNameAlias);
 
@@ -419,6 +424,7 @@ private:
     friend class TestKoColorSpaceSanity;
     friend class TestColorConversionSystem;
     friend struct FriendOfColorSpaceRegistry;
+    friend class TestProfileGeneration;
 
     /**
      * @return a list with an instance of all color space with their default profile.
@@ -434,6 +440,8 @@ private:
      */
     const KoColorConversionSystem* colorConversionSystem() const;
 
+    const KoColorProfile *profileForInternal(const KoColorProfileQuery &query, const bool generate) const;
+
 private:
     KoColorSpaceRegistry(const KoColorSpaceRegistry&);
     KoColorSpaceRegistry operator=(const KoColorSpaceRegistry&);
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp
index d60204790d2..8bf3fd7936b 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp
@@ -118,6 +118,15 @@ void RgbF16ColorSpace::modulateLightnessByGrayBrush(quint8 *dst, const QRgb *bru
    modulateLightnessByGrayBrushRGB<KoRgbF16Traits>(dst, brush, strength, nPixels);
 }
 
+QList<KoColorProfileQuery> RgbF16ColorSpaceFactory::requiredConnectionProfiles(const KoColorProfile *profile) const
+{
+    if (profile->getTransferCharacteristics() == TRC_ITU_R_BT_2100_0_PQ
+        && profile->getColorPrimaries() != PRIMARIES_UNSPECIFIED) {
+        return {KoColorProfileQuery(profile->getColorPrimaries(), TRC_LINEAR)};
+    }
+    return {};
+}
+
 QList<KoColorConversionTransformationFactory *> RgbF16ColorSpaceFactory::colorConversionLinksFromProfile(const KoColorProfile *profile) const
 {
     if (profile->getTransferCharacteristics() == TRC_ITU_R_BT_2100_0_PQ
@@ -125,9 +134,13 @@ QList<KoColorConversionTransformationFactory *> RgbF16ColorSpaceFactory::colorCo
 
         KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
         KoColorProfileQuery query(profile->getColorPrimaries(), TRC_LINEAR);
-        QString linear = registry->profileFor(query)->name();
 
-        LcmsRGBP2020PQColorSpaceFactoryWrapper<RgbF16ColorSpaceFactory> factory(profile->name(), linear);
+        /// we cannot generate a profile at this stage, it should have been generated
+        /// by the registry thanks to the call to requiredConnectionProfiles()
+        const KoColorProfile *connectionProfile = registry->profileFor(query, false);
+        KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(connectionProfile, QList<KoColorConversionTransformationFactory *>());
+
+        LcmsRGBP2020PQColorSpaceFactoryWrapper<RgbF16ColorSpaceFactory> factory(profile->name(), connectionProfile->name());
         return factory.colorConversionLinks();
     } else if (profile->name() == "High Dynamic Range UHDTV Wide Color Gamut Display (Rec. 2020) - SMPTE ST 2084 PQ EOTF") {
         KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.h b/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.h
index 50a08005f25..c0c69ca2cd9 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.h
+++ b/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.h
@@ -107,6 +107,7 @@ public:
         return true;
     }
 
+    QList<KoColorProfileQuery> requiredConnectionProfiles(const KoColorProfile *profile) const override;
     QList<KoColorConversionTransformationFactory*> colorConversionLinksFromProfile(const KoColorProfile *profile) const override;
 
 };
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp
index 9859cebb9cd..11edcc2f39d 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp
@@ -122,6 +122,14 @@ void RgbF32ColorSpace::modulateLightnessByGrayBrush(quint8 *dst, const QRgb *bru
     modulateLightnessByGrayBrushRGB<KoRgbF32Traits>(dst, brush, strength, nPixels);
 }
 
+QList<KoColorProfileQuery> RgbF32ColorSpaceFactory::requiredConnectionProfiles(const KoColorProfile *profile) const
+{
+    if (profile->getTransferCharacteristics() == TRC_ITU_R_BT_2100_0_PQ
+        && profile->getColorPrimaries() != PRIMARIES_UNSPECIFIED) {
+        return {KoColorProfileQuery(profile->getColorPrimaries(), TRC_LINEAR)};
+    }
+    return {};
+}
 
 QList<KoColorConversionTransformationFactory *> RgbF32ColorSpaceFactory::colorConversionLinksFromProfile(const KoColorProfile *profile) const
 {
@@ -130,9 +138,13 @@ QList<KoColorConversionTransformationFactory *> RgbF32ColorSpaceFactory::colorCo
 
         KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
         KoColorProfileQuery query(profile->getColorPrimaries(), TRC_LINEAR);
-        QString linear = registry->profileFor(query)->name();
 
-        LcmsRGBP2020PQColorSpaceFactoryWrapper<RgbF32ColorSpaceFactory> factory(profile->name(), linear);
+        /// we cannot generate a profile at this stage, it should have been generated
+        /// by the registry thanks to the call to requiredConnectionProfiles()
+        const KoColorProfile *connectionProfile = registry->profileFor(query, false);
+        KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(connectionProfile, QList<KoColorConversionTransformationFactory *>());
+
+        LcmsRGBP2020PQColorSpaceFactoryWrapper<RgbF32ColorSpaceFactory> factory(profile->name(), connectionProfile->name());
         return factory.colorConversionLinks();
     } else if (profile->name() == "High Dynamic Range UHDTV Wide Color Gamut Display (Rec. 2020) - SMPTE ST 2084 PQ EOTF") {
         KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.h b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.h
index d43301b21b8..b215de1c52a 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.h
+++ b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.h
@@ -107,6 +107,7 @@ public:
         return true;
     }
 
+    QList<KoColorProfileQuery> requiredConnectionProfiles(const KoColorProfile *profile) const override;
     QList<KoColorConversionTransformationFactory*> colorConversionLinksFromProfile(const KoColorProfile *profile) const override;
 
 };
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp
index ff94971bd27..922e8edab5c 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp
@@ -115,6 +115,15 @@ void RgbU16ColorSpace::modulateLightnessByGrayBrush(quint8 *dst, const QRgb *bru
    modulateLightnessByGrayBrushRGB<KoBgrU16Traits>(dst, brush, strength, nPixels);
 }
 
+QList<KoColorProfileQuery> RgbU16ColorSpaceFactory::requiredConnectionProfiles(const KoColorProfile *profile) const
+{
+    if (profile->getTransferCharacteristics() == TRC_ITU_R_BT_2100_0_PQ
+        && profile->getColorPrimaries() != PRIMARIES_UNSPECIFIED) {
+        return {KoColorProfileQuery(profile->getColorPrimaries(), TRC_LINEAR)};
+    }
+    return {};
+}
+
 QList<KoColorConversionTransformationFactory *> RgbU16ColorSpaceFactory::colorConversionLinksFromProfile(const KoColorProfile *profile) const
 {
     if (profile->getTransferCharacteristics() == TRC_ITU_R_BT_2100_0_PQ
@@ -122,9 +131,13 @@ QList<KoColorConversionTransformationFactory *> RgbU16ColorSpaceFactory::colorCo
 
         KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
         KoColorProfileQuery query(profile->getColorPrimaries(), TRC_LINEAR);
-        QString linear = registry->profileFor(query)->name();
 
-        LcmsRGBP2020PQColorSpaceFactoryWrapper<RgbU16ColorSpaceFactory> factory(profile->name(), linear);
+        /// we cannot generate a profile at this stage, it should have been generated
+        /// by the registry thanks to the call to requiredConnectionProfiles()
+        const KoColorProfile *connectionProfile = registry->profileFor(query, false);
+        KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(connectionProfile, QList<KoColorConversionTransformationFactory *>());
+
+        LcmsRGBP2020PQColorSpaceFactoryWrapper<RgbU16ColorSpaceFactory> factory(profile->name(), connectionProfile->name());
         return factory.colorConversionLinks();
     } else if (profile->name() == "High Dynamic Range UHDTV Wide Color Gamut Display (Rec. 2020) - SMPTE ST 2084 PQ EOTF") {
         KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.h b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.h
index 83e4a848ff4..1d05cbf66db 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.h
+++ b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.h
@@ -91,6 +91,7 @@ public:
         return "sRGB-elle-V2-g10.icc";//this is a linear space, because 16bit is enough to only enjoy advantages of linear space
     }
 
+    QList<KoColorProfileQuery> requiredConnectionProfiles(const KoColorProfile *profile) const override;
     QList<KoColorConversionTransformationFactory*> colorConversionLinksFromProfile(const KoColorProfile *profile) const override;
 };
 
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp
index e38472296ee..3b55083a22a 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp
@@ -130,6 +130,15 @@ void RgbU8ColorSpace::modulateLightnessByGrayBrush(quint8 *dst, const QRgb *brus
     modulateLightnessByGrayBrushRGB<KoBgrU8Traits>(dst, brush, strength, nPixels);
 }
 
+QList<KoColorProfileQuery> RgbU8ColorSpaceFactory::requiredConnectionProfiles(const KoColorProfile *profile) const
+{
+    if (profile->getTransferCharacteristics() == TRC_ITU_R_BT_2100_0_PQ
+        && profile->getColorPrimaries() != PRIMARIES_UNSPECIFIED) {
+        return {KoColorProfileQuery(profile->getColorPrimaries(), TRC_LINEAR)};
+    }
+    return {};
+}
+
 QList<KoColorConversionTransformationFactory*> RgbU8ColorSpaceFactory::colorConversionLinksFromProfile(const KoColorProfile *profile) const
 {
     if (profile->getTransferCharacteristics() == TRC_ITU_R_BT_2100_0_PQ
@@ -137,9 +146,13 @@ QList<KoColorConversionTransformationFactory*> RgbU8ColorSpaceFactory::colorConv
 
         KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
         KoColorProfileQuery query(profile->getColorPrimaries(), TRC_LINEAR);
-        QString linear = registry->profileFor(query)->name();
 
-        LcmsRGBP2020PQColorSpaceFactoryWrapper<RgbU8ColorSpaceFactory> factory(profile->name(), linear);
+        /// we cannot generate a profile at this stage, it should have been generated
+        /// by the registry thanks to the call to requiredConnectionProfiles()
+        const KoColorProfile *connectionProfile = registry->profileFor(query, false);
+        KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(connectionProfile, QList<KoColorConversionTransformationFactory *>());
+
+        LcmsRGBP2020PQColorSpaceFactoryWrapper<RgbU8ColorSpaceFactory> factory(profile->name(), connectionProfile->name());
         return factory.colorConversionLinks();
     } else if (profile->name() == "High Dynamic Range UHDTV Wide Color Gamut Display (Rec. 2020) - SMPTE ST 2084 PQ EOTF") {
         KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.h b/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.h
index e1b327051fe..6542f3c1e8a 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.h
+++ b/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.h
@@ -105,6 +105,7 @@ public:
         return "sRGB-elle-V2-srgbtrc.icc";
     }
 
+    QList<KoColorProfileQuery> requiredConnectionProfiles(const KoColorProfile *profile) const override;
     QList<KoColorConversionTransformationFactory*> colorConversionLinksFromProfile(const KoColorProfile *profile) const override;
 };
 
diff --git a/plugins/color/lcms2engine/tests/TestProfileGeneration.cpp b/plugins/color/lcms2engine/tests/TestProfileGeneration.cpp
index 007945f039f..7fa9c508845 100644
--- a/plugins/color/lcms2engine/tests/TestProfileGeneration.cpp
+++ b/plugins/color/lcms2engine/tests/TestProfileGeneration.cpp
@@ -415,21 +415,57 @@ void TestProfileGeneration::testCICPwriting()
 
 void TestProfileGeneration::testRetrieveNits_data()
 {
-    QTest::addColumn<double>("expectedReferenceWhite");
-    QTest::addRow("rec2100PQ 80nits") << 80.0;
-    QTest::addRow("rec2100PQ 203nits") << 203.0;
+    QTest::addColumn<ColorPrimaries>("primaries");
+    QTest::addColumn<TransferCharacteristics>("transfer");
+    QTest::addColumn<qreal>("requestedReferenceWhite");
+    QTest::addColumn<qreal>("expectedReferenceWhite");
+
+    // SDR profiles should have no hdr reference white value
+    QTest::addRow("rec709-srgbtrc") << PRIMARIES_ITU_R_BT_709_5 << TRC_IEC_61966_2_1 << -1.0 << -1.0;
+
+    // Scene-linear profiles should also have **no** reference white value, since it
+    // is meaningless for them, their value of 1.0 is pinned to the reference white point
+    QTest::addRow("rec2020-g10") << PRIMARIES_ITU_R_BT_2020_2_AND_2100_0 << TRC_LINEAR << -1.0 << -1.0;
+    QTest::addRow("rec709-g10") << PRIMARIES_ITU_R_BT_709_5 << TRC_LINEAR << -1.0 << -1.0;
+
+    // HDR profiles should have one
+
+    QTest::addRow("rec2100 PQ default nits") << PRIMARIES_ITU_R_BT_2020_2_AND_2100_0 << TRC_ITU_R_BT_2100_0_PQ << -1.0 << 203.0;
+    QTest::addRow("rec2100 PQ 80 nits") << PRIMARIES_ITU_R_BT_2020_2_AND_2100_0 << TRC_ITU_R_BT_2100_0_PQ << 80.0 << 80.0;
+    QTest::addRow("rec2100 PQ 203 nits") << PRIMARIES_ITU_R_BT_2020_2_AND_2100_0 << TRC_ITU_R_BT_2100_0_PQ << 203.0 << 203.0;
+
+    // Display P3 profile is generated on the fly
+    QTest::addRow("display-p3 srgbtrc") << PRIMARIES_SMPTE_EG_432_1 << TRC_IEC_61966_2_1 << -1.0 << -1.0;
+
+    // Display P3 PQ is also generated on the fly and should not cause any deadlocks
+    // when connection linear profiles are added to the registry
+    QTest::addRow("display-p3 PQ default nits") << PRIMARIES_SMPTE_EG_432_1 << TRC_ITU_R_BT_2100_0_PQ << -1.0 << 203.0;
+    QTest::addRow("display-p3 PQ 80 nits") << PRIMARIES_SMPTE_EG_432_1 << TRC_ITU_R_BT_2100_0_PQ << 80.0 << 80.0;
+    QTest::addRow("display-p3 PQ 203 nits") << PRIMARIES_SMPTE_EG_432_1 << TRC_ITU_R_BT_2100_0_PQ << 203.0 << 203.0;
 }
 
 void TestProfileGeneration::testRetrieveNits()
 {
-    QFETCH(double, expectedReferenceWhite);
-    KoColorProfileQuery query(PRIMARIES_ITU_R_BT_2020_2_AND_2100_0, TRC_ITU_R_BT_2100_0_PQ);
-    query.hdrReferenceWhite = std::make_optional(expectedReferenceWhite);
-    const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileFor(query);
-
+    QFETCH(ColorPrimaries, primaries);
+    QFETCH(TransferCharacteristics, transfer);
+    QFETCH(qreal, requestedReferenceWhite);
+    QFETCH(qreal, expectedReferenceWhite);
+    KoColorProfileQuery query(primaries, transfer);
+    if (requestedReferenceWhite > 0) {
+        query.hdrReferenceWhite = requestedReferenceWhite;
+    }
+    const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileForInternal(query, true);
     QVERIFY(profile);
-    QVERIFY(profile->hdrReferenceWhite());
-    QVERIFY(qFuzzyCompare(*profile->hdrReferenceWhite(), expectedReferenceWhite));
+
+    QCOMPARE(profile->getColorPrimaries(), primaries);
+    QCOMPARE(profile->getTransferCharacteristics(), transfer);
+
+    if (expectedReferenceWhite > 0) {
+        QVERIFY(profile->hdrReferenceWhite().has_value());
+        QCOMPARE(*profile->hdrReferenceWhite(), expectedReferenceWhite);
+    } else {
+        QVERIFY(!profile->hdrReferenceWhite().has_value());
+    }
 }
 
 KISTEST_MAIN(TestProfileGeneration)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.