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.
This commit is contained in:
Xaver Hugl 2024-01-18 21:51:40 +01:00
parent 788c186701
commit 36bec2d941
5 changed files with 39 additions and 28 deletions

View file

@ -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) {

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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());
}
}

View file

@ -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;