[graphics/krita] /: Implement retrieving the perceptual quantizer reference white from the profile.
Wolthera van Hövell <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit b1ed72d50f7d9ee3bd6beeea60b4a6d52955d816 by Wolthera van Hövell, on behalf of Wolthera van Hövell tot Westerflier.
Committed on 16/07/2026 at 16:26.
Pushed by woltherav into branch 'master'.
Implement retrieving the perceptual quantizer reference white from the profile.
This way, we can finally support different nits values for the perceptual quantizer
curve, which is necessary if we want to shift to 203 nits by default.
M +9 -0 libs/pigment/KoColorProfile.h
M +2 -0 libs/pigment/KoColorProfileQuery.h
M +6 -6 libs/pigment/KoColorTransferFunctions.h
M +5 -0 libs/pigment/colorprofiles/KoDummyColorProfile.cpp
M +1 -0 libs/pigment/colorprofiles/KoDummyColorProfile.h
M +0 -1 plugins/color/lcms2engine/IccColorSpaceEngine.cpp
M +10 -5 plugins/color/lcms2engine/LcmsEnginePlugin.cpp
M +29 -8 plugins/color/lcms2engine/LcmsRGBP2020PQColorSpaceTransformation.h
M +10 -2 plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp
M +2 -0 plugins/color/lcms2engine/colorprofiles/IccColorProfile.h
M +16 -1 plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp
M +1 -0 plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.h
M +1 -1 plugins/color/lcms2engine/colorprofiles/LcmsPredefinedPipelineFunctions.cpp
https://invent.kde.org/graphics/krita/-/commit/b1ed72d50f7d9ee3bd6beeea60b4a6d52955d816
diff --git a/libs/pigment/KoColorProfile.h b/libs/pigment/KoColorProfile.h
index ad4c29d2ad8..1d5abf90a34 100644
--- a/libs/pigment/KoColorProfile.h
+++ b/libs/pigment/KoColorProfile.h
@@ -150,6 +150,15 @@ public:
* @return a KoColorimetryUtils::xyY with the whitepoint in xyY
*/
virtual KoColorimetryUtils::xyY getWhitePointxyY() const = 0;
+
+ /**
+ * @brief hdrReferenceWhite
+ * HDR reference white is only available for Perceptual Quantizer profiles that
+ * save this value in their dictionary. It is used to scale the value encoded
+ * by the perceptual quantizer curve. When missing, assume a value of 203.
+ * @return the HDR reference white in cd/m²
+ */
+ virtual std::optional<double> hdrReferenceWhite() const = 0;
/**
* @return estimated gamma for RGB and Grayscale profiles
diff --git a/libs/pigment/KoColorProfileQuery.h b/libs/pigment/KoColorProfileQuery.h
index eb9c9ddc9c8..36b740e2aef 100644
--- a/libs/pigment/KoColorProfileQuery.h
+++ b/libs/pigment/KoColorProfileQuery.h
@@ -28,6 +28,8 @@ struct KoColorProfileQuery {
ColorPrimaries primaries; /// CICP-compatible enum value representing the white point and primaries.
TransferCharacteristics transfer; /// CICP-compatible enum value representing the transfer function.
+ std::optional<double> hdrReferenceWhite; /// Used for PQ profiles.
+
inline bool isValid() const {
return isGrayscale() || isRgb();
}
diff --git a/libs/pigment/KoColorTransferFunctions.h b/libs/pigment/KoColorTransferFunctions.h
index 0f88482e0ee..7154c1dda10 100644
--- a/libs/pigment/KoColorTransferFunctions.h
+++ b/libs/pigment/KoColorTransferFunctions.h
@@ -35,7 +35,7 @@ static constexpr float multiplier16bit = 1.0f / max16bit;
enum class ConversionPolicy { KeepTheSame, ApplyPQ, ApplyHLG, ApplySMPTE428 };
-ALWAYS_INLINE float applySmpte2048Curve(float x) noexcept
+ALWAYS_INLINE float applySmpte2048Curve(float x, float refWhite = 203.0) noexcept
{
const float m1 = 2610.0f / 4096.0f / 4.0f;
const float m2 = 2523.0f / 4096.0f * 128.0f;
@@ -43,12 +43,12 @@ ALWAYS_INLINE float applySmpte2048Curve(float x) noexcept
const float c2 = 2413.0f / 4096.0f * 32.0f;
const float c3 = 2392.0f / 4096.0f * 32.0f;
const float a4 = 1.0f;
- const float x_p = powf(0.008f * std::max(0.0f, x), m1);
+ const float x_p = powf((refWhite/10000.0f) * std::max(0.0f, x), m1);
const float res = powf((a1 + c2 * x_p) / (a4 + c3 * x_p), m2);
return res;
}
-ALWAYS_INLINE float removeSmpte2048Curve(float x) noexcept
+ALWAYS_INLINE float removeSmpte2048Curve(float x, float refWhite = 203.0) noexcept
{
const float m1_r = 4096.0f * 4.0f / 2610.0f;
const float m2_r = 4096.0f / 2523.0f / 128.0f;
@@ -58,7 +58,7 @@ ALWAYS_INLINE float removeSmpte2048Curve(float x) noexcept
const float x_p = powf(x, m2_r);
const float res = powf(qMax(0.0f, x_p - a1) / (c2 - c3 * x_p), m1_r);
- return res * 125.0f;
+ return res * (10000.0f/refWhite);
}
// From ITU Bt. 2390-8 pg. 31, this calculates the gamma for the nominal peak.
@@ -159,7 +159,7 @@ template<typename Arch>
struct KoColorTransferFunctions {
using float_v = typename KoStreamedMath<Arch>::float_v;
- static ALWAYS_INLINE void removeSmpte2048Curve(float_v &x) noexcept
+ static ALWAYS_INLINE void removeSmpte2048Curve(float_v &x, float_v refWhite = 203.0) noexcept
{
constexpr float m1_r = 4096.0f * 4.0f / 2610.0f;
constexpr float m2_r = 4096.0f / 2523.0f / 128.0f;
@@ -171,7 +171,7 @@ struct KoColorTransferFunctions {
const float_v res =
xsimd::pow(xsimd::max(float_v(0.0f), x_p - a1) / (c2 - c3 * x_p),
float_v(m1_r));
- x = res * 125.0f;
+ x = res * (10000.0f/refWhite);
}
static ALWAYS_INLINE void removeHLGCurve(float_v &x) noexcept
diff --git a/libs/pigment/colorprofiles/KoDummyColorProfile.cpp b/libs/pigment/colorprofiles/KoDummyColorProfile.cpp
index 6277c750aa9..9b6ea89efd4 100644
--- a/libs/pigment/colorprofiles/KoDummyColorProfile.cpp
+++ b/libs/pigment/colorprofiles/KoDummyColorProfile.cpp
@@ -111,6 +111,11 @@ KoColorimetryUtils::xyY KoDummyColorProfile::getWhitePointxyY() const
return KoColorimetryUtils::xyY{0.34773, 0.35952, 1.0};
}
+std::optional<double> KoDummyColorProfile::hdrReferenceWhite() const
+{
+ return std::nullopt;
+}
+
QVector <double> KoDummyColorProfile::getEstimatedTRC() const
{
diff --git a/libs/pigment/colorprofiles/KoDummyColorProfile.h b/libs/pigment/colorprofiles/KoDummyColorProfile.h
index cfb3b49c419..9ec1f95ff20 100644
--- a/libs/pigment/colorprofiles/KoDummyColorProfile.h
+++ b/libs/pigment/colorprofiles/KoDummyColorProfile.h
@@ -33,6 +33,7 @@ public:
QVector <KoColorimetryUtils::xyY> getColorantsxyY() const override;
KoColorimetryUtils::XYZ getWhitePointXYZ() const override;
KoColorimetryUtils::xyY getWhitePointxyY() const override;
+ std::optional<double> hdrReferenceWhite() const override;
QVector <double> getEstimatedTRC() const override;
bool compareTRC(TransferCharacteristics characteristics, float error) const override;
void linearizeFloatValue(QVector <double> & Value) const override;
diff --git a/plugins/color/lcms2engine/IccColorSpaceEngine.cpp b/plugins/color/lcms2engine/IccColorSpaceEngine.cpp
index be8f72b8142..a7355947e9c 100644
--- a/plugins/color/lcms2engine/IccColorSpaceEngine.cpp
+++ b/plugins/color/lcms2engine/IccColorSpaceEngine.cpp
@@ -14,7 +14,6 @@
#include <kis_assert.h>
#include "LcmsColorSpace.h"
-#include "LcmsRGBP2020PQColorSpace.h"
#include "RgbU8ColorSpace.h"
// -- KoLcmsColorConversionTransformation --
diff --git a/plugins/color/lcms2engine/LcmsEnginePlugin.cpp b/plugins/color/lcms2engine/LcmsEnginePlugin.cpp
index fbe6db5de79..96f239def9c 100644
--- a/plugins/color/lcms2engine/LcmsEnginePlugin.cpp
+++ b/plugins/color/lcms2engine/LcmsEnginePlugin.cpp
@@ -51,8 +51,6 @@
#include "colorspaces/ycbcr_u16/YCbCrU16ColorSpace.h"
#include "colorspaces/ycbcr_f32/YCbCrF32ColorSpace.h"
-#include "LcmsRGBP2020PQColorSpace.h"
-
#include <KoConfig.h>
#ifdef HAVE_OPENEXR
@@ -168,8 +166,15 @@ LcmsEnginePlugin::LcmsEnginePlugin(QObject *parent, const QVariantList &)
KoColorProfile *rgbProfile = LcmsColorProfileContainer::createFromLcmsProfile(cmsCreate_sRGBProfile());
registry->addProfile(rgbProfile);
- KoColorProfile *rec2100pq = new IccColorProfile(KoColorProfileQuery(PRIMARIES_ITU_R_BT_2020_2_AND_2100_0, TRC_ITU_R_BT_2100_0_PQ));
- registry->addProfile(rec2100pq);
+ KoColorProfileQuery query(PRIMARIES_ITU_R_BT_2020_2_AND_2100_0, TRC_ITU_R_BT_2100_0_PQ);
+ query.hdrReferenceWhite = std::make_optional(203.0);
+
+ KoColorProfile *rec2100pq203nits = new IccColorProfile(query);
+ registry->addProfile(rec2100pq203nits);
+
+ query.hdrReferenceWhite = std::make_optional(80.0);
+ KoColorProfile *rec2100pq80nits = new IccColorProfile(query);
+ registry->addProfile(rec2100pq80nits);
/**
* By default we remap the legacy HDR profile into the new Rec2020PQ
@@ -179,7 +184,7 @@ LcmsEnginePlugin::LcmsEnginePlugin(QObject *parent, const QVariantList &)
* Krita previously.
*/
const QString legacyRec2020PQProfileName = "High Dynamic Range UHDTV Wide Color Gamut Display (Rec. 2020) - SMPTE ST 2084 PQ EOTF";
- registry->addProfileAlias(legacyRec2020PQProfileName, rec2100pq->name());
+ registry->addProfileAlias(legacyRec2020PQProfileName, rec2100pq203nits->name());
registry->add(new RgbU8ColorSpaceFactory());
registry->add(new RgbU16ColorSpaceFactory());
diff --git a/plugins/color/lcms2engine/LcmsRGBP2020PQColorSpaceTransformation.h b/plugins/color/lcms2engine/LcmsRGBP2020PQColorSpaceTransformation.h
index 3d38c85d592..911d883b4d7 100644
--- a/plugins/color/lcms2engine/LcmsRGBP2020PQColorSpaceTransformation.h
+++ b/plugins/color/lcms2engine/LcmsRGBP2020PQColorSpaceTransformation.h
@@ -48,33 +48,52 @@ struct DstTraitsForSource<KoBgrU8Traits> {
template <typename src_channel_type,
typename dst_channel_type>
struct RemoveSmpte2048Policy {
- static ALWAYS_INLINE dst_channel_type process(src_channel_type value) {
+ static ALWAYS_INLINE dst_channel_type process(src_channel_type value, float refWhite) {
return
KoColorSpaceMaths<float, dst_channel_type>::scaleToA(
removeSmpte2048Curve(
KoColorSpaceMaths<src_channel_type, float>::scaleToA(
- value)));
+ value), refWhite));
+ }
+
+ static ALWAYS_INLINE double getRefWhite(const KoColorSpace *src, const KoColorSpace *target) {
+ Q_UNUSED(target)
+ if (!src || !src->profile()) return 203.0;
+ return src->profile()->hdrReferenceWhite()? *src->profile()->hdrReferenceWhite(): 203.0;
}
};
template <typename src_channel_type,
typename dst_channel_type>
struct ApplySmpte2048Policy {
- static ALWAYS_INLINE dst_channel_type process(src_channel_type value) {
+ static ALWAYS_INLINE dst_channel_type process(src_channel_type value, float refWhite) {
return
KoColorSpaceMaths<float, dst_channel_type>::scaleToA(
applySmpte2048Curve(
KoColorSpaceMaths<src_channel_type, float>::scaleToA(
- value)));
+ value), refWhite));
+ }
+
+ static ALWAYS_INLINE double getRefWhite(const KoColorSpace *src, const KoColorSpace *target) {
+ Q_UNUSED(src)
+ if (!target || !target->profile()) return 203.0;
+ return target->profile()->hdrReferenceWhite()? *target->profile()->hdrReferenceWhite(): 203.0;
}
};
template <typename src_channel_type,
typename dst_channel_type>
struct NoopPolicy {
- static ALWAYS_INLINE dst_channel_type process(src_channel_type value) {
+ static ALWAYS_INLINE dst_channel_type process(src_channel_type value, float refWhite) {
+ Q_UNUSED(refWhite)
return KoColorSpaceMaths<src_channel_type, dst_channel_type>::scaleToA(value);
}
+
+ static ALWAYS_INLINE double getRefWhite(const KoColorSpace *src, const KoColorSpace *target) {
+ Q_UNUSED(src)
+ Q_UNUSED(target)
+ return 203.0;
+ }
};
}
@@ -105,10 +124,12 @@ struct ApplyRgbShaper : public KoColorConversionTransformation
typedef typename DstCSTraits::channels_type dst_channel_type;
typedef Policy<src_channel_type, dst_channel_type> ConcretePolicy;
+ const double refWhite = ConcretePolicy::getRefWhite(srcColorSpace(), dstColorSpace());
+
for (int i = 0; i < nPixels; i++) {
- dstPixel->red = ConcretePolicy::process(srcPixel->red);
- dstPixel->green = ConcretePolicy::process(srcPixel->green);
- dstPixel->blue = ConcretePolicy::process(srcPixel->blue);
+ dstPixel->red = ConcretePolicy::process(srcPixel->red, refWhite);
+ dstPixel->green = ConcretePolicy::process(srcPixel->green, refWhite);
+ dstPixel->blue = ConcretePolicy::process(srcPixel->blue, refWhite);
dstPixel->alpha =
KoColorSpaceMaths<src_channel_type, dst_channel_type>::scaleToA(
srcPixel->alpha);
diff --git a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp
index af14d114d69..4f360e0d78f 100644
--- a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp
+++ b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp
@@ -173,8 +173,8 @@ IccColorProfile::IccColorProfile(const KoColorProfileQuery &query)
cmsCIEXYZ media_blackpoint = {0.0, 0.0, 0.0};
cmsWriteTag (iccProfile, cmsSigMediaBlackPointTag, &media_blackpoint);
- if (query.transfer == TRC_ITU_R_BT_2100_0_PQ) {
- double nits = 80.0;
+ if (query.transfer == TRC_ITU_R_BT_2100_0_PQ && query.primaries != PRIMARIES_UNSPECIFIED) {
+ double nits = query.hdrReferenceWhite? *query.hdrReferenceWhite: 203.0;
LcmsPredefinedPipelineFunctions::setPerceptualQuantizerAToBDummyPipeline(iccProfile, cmsSigAToB0Tag, query.primaries, nits);
LcmsPredefinedPipelineFunctions::setPerceptualQuantizerBToADummyPipeline(iccProfile, cmsSigBToA0Tag, query.primaries, nits);
LcmsPredefinedPipelineFunctions::setDiffuseWhitePerceptualQuantizer(iccProfile, nits);
@@ -410,6 +410,14 @@ KoColorimetryUtils::xyY IccColorProfile::getWhitePointxyY() const
}
return KoColorimetryUtils::xyY{0.34773, 0.35952, 1.0};
}
+
+std::optional<double> IccColorProfile::hdrReferenceWhite() const
+{
+ if (d->shared->lcmsProfile) {
+ return d->shared->lcmsProfile->hdrReferenceWhite();
+ }
+ return std::nullopt;
+}
QVector <qreal> IccColorProfile::getEstimatedTRC() const
{
QVector <qreal> dummy(3);
diff --git a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.h b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.h
index e64f414b83a..b5c3e7a2596 100644
--- a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.h
+++ b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.h
@@ -64,6 +64,7 @@ public:
virtual bool hasColorants() const = 0;
virtual QVector <KoColorimetryUtils::XYZ> getColorantsXYZ() const = 0;
virtual QVector <KoColorimetryUtils::xyY> getColorantsxyY() const = 0;
+ virtual std::optional<double> hdrReferenceWhite() const = 0;
virtual KoColorimetryUtils::XYZ getWhitePointXYZ() const = 0;
virtual KoColorimetryUtils::xyY getWhitePointxyY() const = 0;
virtual QVector <double> getEstimatedTRC() const = 0;
@@ -109,6 +110,7 @@ public:
QVector <KoColorimetryUtils::xyY> getColorantsxyY() const override;
KoColorimetryUtils::XYZ getWhitePointXYZ() const override;
KoColorimetryUtils::xyY getWhitePointxyY() const override;
+ std::optional<double> hdrReferenceWhite() const override;
QVector <qreal> getEstimatedTRC() const override;
bool compareTRC(TransferCharacteristics characteristics, float error) const override;
void linearizeFloatValue(QVector <qreal> & Value) const override;
diff --git a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp
index 6a7dd6c9914..df618e40801 100644
--- a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp
+++ b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp
@@ -21,6 +21,7 @@
#include "DebugPigment.h"
#include "kis_debug.h"
+#include "kis_dom_utils.h"
#include <KisLazyStorage.h>
#include <KisLazyValueWrapper.h>
@@ -84,6 +85,7 @@ public:
bool adaptedFromD50;
cmsCIEXYZ mediaWhitePoint;
cmsCIExyY whitePoint;
+ std::optional<double> hdrReferenceWhite;
cmsCIEXYZTRIPLE colorants;
cmsToneCurve *redTRC {0};
cmsToneCurve *greenTRC {0};
@@ -323,7 +325,15 @@ bool LcmsColorProfileContainer::init()
cmsHANDLE dictionary = (cmsHANDLE) cmsReadTag(d->profile, cmsSigMetaTag);
const cmsDICTentry *entry = cmsDictGetEntryList(dictionary);
while (entry) {
- dbgPigment << QString::fromWCharArray(entry->Name, -1) << QString::fromWCharArray(entry->Value, -1);;
+ QString name = QString::fromWCharArray(entry->Name, -1);
+ QString value = QString::fromWCharArray(entry->Value, -1);
+ if (name.toLower() == "crwl") {
+ // https://registry.color.org/dicttype-metadata/crwl
+ double hdrReferenceWhite = KisDomUtils::toDouble(value);
+ d->hdrReferenceWhite = std::make_optional(hdrReferenceWhite);
+
+ }
+ dbgPigment << name << value;
dbgPigment << entry->DisplayName << entry->DisplayValue;
entry = cmsDictNextEntry(entry);
}
@@ -480,6 +490,11 @@ KoColorimetryUtils::xyY LcmsColorProfileContainer::getWhitePointxyY() const
return fromCIExyY(d->whitePoint);
}
+std::optional<double> LcmsColorProfileContainer::hdrReferenceWhite() const
+{
+ return d->hdrReferenceWhite;
+}
+
QVector <double> LcmsColorProfileContainer::getEstimatedTRC() const
{
QVector <double> TRCtriplet(3);
diff --git a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.h b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.h
index 1f00bfbc8f0..3ef6e8ea260 100644
--- a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.h
+++ b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.h
@@ -87,6 +87,7 @@ public:
QVector <KoColorimetryUtils::xyY> getColorantsxyY() const override;
KoColorimetryUtils::XYZ getWhitePointXYZ() const override;
KoColorimetryUtils::xyY getWhitePointxyY() const override;
+ std::optional<double> hdrReferenceWhite() const override;
QVector <double> getEstimatedTRC() const override;
virtual void LinearizeFloatValue(QVector <double> & Value) const;
virtual void DelinearizeFloatValue(QVector <double> & Value) const;
diff --git a/plugins/color/lcms2engine/colorprofiles/LcmsPredefinedPipelineFunctions.cpp b/plugins/color/lcms2engine/colorprofiles/LcmsPredefinedPipelineFunctions.cpp
index cb246f01ab8..223ade0f26a 100644
--- a/plugins/color/lcms2engine/colorprofiles/LcmsPredefinedPipelineFunctions.cpp
+++ b/plugins/color/lcms2engine/colorprofiles/LcmsPredefinedPipelineFunctions.cpp
@@ -109,7 +109,7 @@ struct perceptualDummyHelper {
*/
cmsInt32Number samplePQDummyClut(const cmsUInt16Number In[], cmsUInt16Number Out[], void *Cargo) {
struct perceptualDummyHelper *helper = (struct perceptualDummyHelper *) Cargo;
- const float pqScale = 125.0; /// Important: this normalizes the pq signal.
+ const float pqScale = helper->diffuseWhiteNits; /// Important: this normalizes the pq signal.
const float nominalPeak = 10000.0/(helper->diffuseWhiteNits * 3.7743);/// scaling the nits value by the HLG scaling factor.
float lin[3];