From 36bec2d941802a6039bc6c845524fd1d233942e6 Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Thu, 18 Jan 2024 21:51:40 +0100 Subject: [PATCH] colors/colordevice: make channel factors linear The redshift table is in gamma 2.2 encoding and not linear, which means that it only yields correct results with 1.0 pixel values. It also means that when it's being applied in linear space in the color management shaders, the result is quite wrong. To fix that, this commit makes the channel factors linear and the backend calculates the nonlinear factors where needed. --- src/backends/drm/drm_output.cpp | 9 ++-- .../x11/standalone/x11_standalone_output.cpp | 2 +- src/colors/colordevice.cpp | 3 +- src/core/colorspace.cpp | 51 +++++++++++-------- src/core/colorspace.h | 2 + 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index 99f8941de2..b54c2b07df 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -437,11 +437,12 @@ bool DrmOutput::doSetChannelFactors(const QVector3D &rgb) if (!m_pipeline->activePending()) { return false; } + const auto inGamma22 = ColorDescription::nitsToEncoded(rgb, NamedTransferFunction::gamma22, 1); if (m_pipeline->hasCTM()) { QMatrix3x3 ctm; - ctm(0, 0) = rgb.x(); - ctm(1, 1) = rgb.y(); - ctm(2, 2) = rgb.z(); + ctm(0, 0) = inGamma22.x(); + ctm(1, 1) = inGamma22.y(); + ctm(2, 2) = inGamma22.z(); m_pipeline->setCTM(ctm); m_pipeline->setGammaRamp(nullptr); if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) { @@ -454,7 +455,7 @@ bool DrmOutput::doSetChannelFactors(const QVector3D &rgb) } } if (m_pipeline->hasGammaRamp()) { - auto lut = ColorTransformation::createScalingTransform(rgb); + auto lut = ColorTransformation::createScalingTransform(inGamma22); if (lut) { m_pipeline->setGammaRamp(std::move(lut)); if (DrmPipeline::commitPipelines({m_pipeline}, DrmPipeline::CommitMode::Test) == DrmPipeline::Error::None) { diff --git a/src/backends/x11/standalone/x11_standalone_output.cpp b/src/backends/x11/standalone/x11_standalone_output.cpp index 862a257711..2987f85b8c 100644 --- a/src/backends/x11/standalone/x11_standalone_output.cpp +++ b/src/backends/x11/standalone/x11_standalone_output.cpp @@ -46,7 +46,7 @@ bool X11Output::setChannelFactors(const QVector3D &rgb) if (m_crtc == XCB_NONE) { return true; } - auto transformation = ColorTransformation::createScalingTransform(rgb); + auto transformation = ColorTransformation::createScalingTransform(ColorDescription::nitsToEncoded(rgb, NamedTransferFunction::gamma22, 1)); if (!transformation) { return false; } diff --git a/src/colors/colordevice.cpp b/src/colors/colordevice.cpp index a5c9835729..b73e9c9660 100644 --- a/src/colors/colordevice.cpp +++ b/src/colors/colordevice.cpp @@ -62,7 +62,8 @@ void ColorDevicePrivate::recalculateFactors() const qreal zWhitePoint = interpolate(blackbodyColor[blackBodyColorIndex + 2], blackbodyColor[blackBodyColorIndex + 5], blendFactor); - temperatureFactors = QVector3D(xWhitePoint, yWhitePoint, zWhitePoint); + // the values in the blackbodyColor array are "gamma corrected", but we need a linear value + temperatureFactors = ColorDescription::encodedToNits(QVector3D(xWhitePoint, yWhitePoint, zWhitePoint), NamedTransferFunction::gamma22, 1); } simpleTransformation = brightnessFactors * temperatureFactors; } diff --git a/src/core/colorspace.cpp b/src/core/colorspace.cpp index f913771ca7..2905b892b7 100644 --- a/src/core/colorspace.cpp +++ b/src/core/colorspace.cpp @@ -344,34 +344,34 @@ static QVector3D clamp(const QVector3D &vect, float min = 0, float max = 1) return QVector3D(std::clamp(vect.x(), min, max), std::clamp(vect.y(), min, max), std::clamp(vect.z(), min, max)); } -QVector3D ColorDescription::mapTo(QVector3D rgb, const ColorDescription &dst) const +QVector3D ColorDescription::encodedToNits(const QVector3D &nits, NamedTransferFunction tf, double sdrBrightness) { - // transfer function -> nits - switch (m_transferFunction) { + switch (tf) { case NamedTransferFunction::sRGB: - rgb = m_sdrBrightness * QVector3D(srgbToLinear(rgb.x()), srgbToLinear(rgb.y()), srgbToLinear(rgb.z())); - break; + return sdrBrightness * QVector3D(srgbToLinear(nits.x()), srgbToLinear(nits.y()), srgbToLinear(nits.z())); case NamedTransferFunction::gamma22: - rgb = m_sdrBrightness * QVector3D(std::pow(rgb.x(), 2.2), std::pow(rgb.y(), 2.2), std::pow(rgb.z(), 2.2)); - break; + return sdrBrightness * QVector3D(std::pow(nits.x(), 2.2), std::pow(nits.y(), 2.2), std::pow(nits.z(), 2.2)); case NamedTransferFunction::linear: - break; + return nits; case NamedTransferFunction::scRGB: - rgb *= 80.0f; - break; + return nits * 80.0f; case NamedTransferFunction::PerceptualQuantizer: - rgb = QVector3D(pqToNits(rgb.x()), pqToNits(rgb.y()), pqToNits(rgb.z())); - break; + return QVector3D(pqToNits(nits.x()), pqToNits(nits.y()), pqToNits(nits.z())); + } + Q_UNREACHABLE(); +} + +QVector3D ColorDescription::nitsToEncoded(const QVector3D &rgb, NamedTransferFunction tf, double sdrBrightness) +{ + switch (tf) { + case NamedTransferFunction::sRGB: { + const auto clamped = clamp(rgb / sdrBrightness); + return QVector3D(linearToSRGB(clamped.x()), linearToSRGB(clamped.y()), linearToSRGB(clamped.z())); + } + case NamedTransferFunction::gamma22: { + const auto clamped = clamp(rgb / sdrBrightness); + return QVector3D(std::pow(clamped.x(), 1 / 2.2), std::pow(clamped.y(), 1 / 2.2), std::pow(clamped.z(), 1 / 2.2)); } - rgb = m_colorimetry.toOther(dst.colorimetry()) * rgb; - // nits -> transfer function - switch (dst.transferFunction()) { - case NamedTransferFunction::sRGB: - rgb = clamp(rgb / dst.sdrBrightness()); - return QVector3D(linearToSRGB(rgb.x()), linearToSRGB(rgb.y()), linearToSRGB(rgb.z())); - case NamedTransferFunction::gamma22: - rgb = clamp(rgb / dst.sdrBrightness()); - return QVector3D(std::pow(rgb.x(), 1 / 2.2), std::pow(rgb.y(), 1 / 2.2), std::pow(rgb.z(), 1 / 2.2)); case NamedTransferFunction::linear: return rgb; case NamedTransferFunction::scRGB: @@ -379,6 +379,13 @@ QVector3D ColorDescription::mapTo(QVector3D rgb, const ColorDescription &dst) co case NamedTransferFunction::PerceptualQuantizer: return QVector3D(nitsToPQ(rgb.x()), nitsToPQ(rgb.y()), nitsToPQ(rgb.z())); } - return QVector3D(); + Q_UNREACHABLE(); +} + +QVector3D ColorDescription::mapTo(QVector3D rgb, const ColorDescription &dst) const +{ + rgb = encodedToNits(rgb, m_transferFunction, m_sdrBrightness); + rgb = m_colorimetry.toOther(dst.colorimetry()) * rgb; + return nitsToEncoded(rgb, dst.transferFunction(), dst.sdrBrightness()); } } diff --git a/src/core/colorspace.h b/src/core/colorspace.h index e39affd0d2..48ab6ad20d 100644 --- a/src/core/colorspace.h +++ b/src/core/colorspace.h @@ -127,6 +127,8 @@ public: * This color description describes display-referred sRGB, with a gamma22 transfer function */ static const ColorDescription sRGB; + static QVector3D encodedToNits(const QVector3D &nits, NamedTransferFunction tf, double sdrBrightness); + static QVector3D nitsToEncoded(const QVector3D &rgb, NamedTransferFunction tf, double sdrBrightness); private: Colorimetry m_colorimetry;