From eae1f513048f54896596dba17a042e5b7aaeb55f Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Fri, 30 Aug 2024 19:04:05 +0200 Subject: [PATCH] core/colorpipeline: refactor tone mapping to be about dimming instead of addnig headroom No functional change, just so that the logic is a bit easier to follow --- src/core/colorpipeline.cpp | 21 ++++++++++++--------- src/core/colorpipeline.h | 6 +++--- src/opengl/colormanagement.glsl | 10 ++++++---- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/core/colorpipeline.cpp b/src/core/colorpipeline.cpp index 97b4739c71..51a4ee71d6 100644 --- a/src/core/colorpipeline.cpp +++ b/src/core/colorpipeline.cpp @@ -37,7 +37,7 @@ ColorPipeline ColorPipeline::create(const ColorDescription &from, const ColorDes // that's not necessarily true, and figuring out the actual range could be complicated.. ret.addMatrix(from.toOther(to, intent), ret.currentOutputRange() * (to.referenceLuminance() / from.referenceLuminance())); if (!s_disableTonemapping && ret.currentOutputRange().max > maxOutputLuminance * 1.01 && intent == RenderingIntent::Perceptual) { - ret.addTonemapper(to.containerColorimetry(), to.referenceLuminance(), ret.currentOutputRange().max, maxOutputLuminance, 1.5); + ret.addTonemapper(to.containerColorimetry(), to.referenceLuminance(), ret.currentOutputRange().max, maxOutputLuminance); } ret.addInverseTransferFunction(to.transferFunction()); @@ -240,7 +240,7 @@ static const QMatrix4x4 s_toICtCp = QMatrix4x4( 0.0, 0.0, 0.0, 1.0).transposed(); static const QMatrix4x4 s_fromICtCp = s_toICtCp.inverted(); -void ColorPipeline::addTonemapper(const Colorimetry &containerColorimetry, double referenceLuminance, double maxInputLuminance, double maxOutputLuminance, double maxAddedHeadroom) +void ColorPipeline::addTonemapper(const Colorimetry &containerColorimetry, double referenceLuminance, double maxInputLuminance, double maxOutputLuminance) { // convert from rgb to ICtCp addMatrix(containerColorimetry.toLMS(), currentOutputRange()); @@ -249,8 +249,8 @@ void ColorPipeline::addTonemapper(const Colorimetry &containerColorimetry, doubl // apply the tone mapping to the intensity component ops.push_back(ColorOp{ .input = currentOutputRange(), - .operation = ColorTonemapper(referenceLuminance, maxInputLuminance, maxOutputLuminance, maxAddedHeadroom), - .output = ValueRange { + .operation = ColorTonemapper(referenceLuminance, maxInputLuminance, maxOutputLuminance), + .output = ValueRange{ .min = currentOutputRange().min, .max = maxOutputLuminance, }, @@ -335,23 +335,26 @@ ColorMultiplier::ColorMultiplier(double factor) { } -ColorTonemapper::ColorTonemapper(double referenceLuminance, double maxInputLuminance, double maxOutputLuminance, double maxAddedHeadroom) +ColorTonemapper::ColorTonemapper(double referenceLuminance, double maxInputLuminance, double maxOutputLuminance) : m_inputReferenceLuminance(referenceLuminance) , m_maxInputLuminance(maxInputLuminance) , m_maxOutputLuminance(maxOutputLuminance) { m_inputRange = maxInputLuminance / referenceLuminance; const double outputRange = maxOutputLuminance / referenceLuminance; - // = how much dynamic range this algorithm adds, by reducing the reference luminance - m_addedRange = std::min(m_inputRange, std::clamp(maxAddedHeadroom / outputRange, 1.0, maxAddedHeadroom)); - m_outputReferenceLuminance = referenceLuminance / m_addedRange; + // how much range we need to at least decently present the content + // 50% HDR headroom should be enough for the tone mapper to do a good enough job, without dimming the image too much + const double minDecentRange = std::min(m_inputRange, 1.5); + // if the output doesn't provide enough HDR headroom for the tone mapper to do a good job, dim the image to create some + m_referenceDimming = 1.0 / std::clamp(outputRange / minDecentRange, 1.0, minDecentRange); + m_outputReferenceLuminance = referenceLuminance * m_referenceDimming; } double ColorTonemapper::map(double pqEncodedLuminance) const { const double luminance = TransferFunction(TransferFunction::PerceptualQuantizer).encodedToNits(pqEncodedLuminance); // keep things linear up to the reference luminance - const double low = std::min(luminance / m_addedRange, m_outputReferenceLuminance); + const double low = std::min(luminance * m_referenceDimming, m_outputReferenceLuminance); // and apply a nonlinear curve above, to reduce the luminance without completely removing differences const double relativeHighlight = std::clamp((luminance / m_inputReferenceLuminance - 1.0) / (m_inputRange - 1.0), 0.0, 1.0); const double high = std::log(relativeHighlight * (std::numbers::e - 1) + 1) * (m_maxOutputLuminance - m_outputReferenceLuminance); diff --git a/src/core/colorpipeline.h b/src/core/colorpipeline.h index 6c6e1d3878..583eadf466 100644 --- a/src/core/colorpipeline.h +++ b/src/core/colorpipeline.h @@ -67,7 +67,7 @@ public: class KWIN_EXPORT ColorTonemapper { public: - explicit ColorTonemapper(double referenceLuminance, double maxInputLuminance, double maxOutputLuminance, double maxAddedHeadroom); + explicit ColorTonemapper(double referenceLuminance, double maxInputLuminance, double maxOutputLuminance); double map(double pqEncodedLuminance) const; bool operator==(const ColorTonemapper &) const = default; @@ -77,7 +77,7 @@ public: double m_maxOutputLuminance; private: double m_inputRange; - double m_addedRange; + double m_referenceDimming; double m_outputReferenceLuminance; }; @@ -118,7 +118,7 @@ public: void addTransferFunction(TransferFunction tf); void addInverseTransferFunction(TransferFunction tf); void addMatrix(const QMatrix4x4 &mat, const ValueRange &output); - void addTonemapper(const Colorimetry &containerColorimetry, double referenceLuminance, double maxInputLuminance, double maxOutputLuminance, double maxAddedHeadroom); + void addTonemapper(const Colorimetry &containerColorimetry, double referenceLuminance, double maxInputLuminance, double maxOutputLuminance); void add(const ColorOp &op); ValueRange inputRange; diff --git a/src/opengl/colormanagement.glsl b/src/opengl/colormanagement.glsl index fb93524d3a..b1f3e6b75d 100644 --- a/src/opengl/colormanagement.glsl +++ b/src/opengl/colormanagement.glsl @@ -114,14 +114,16 @@ vec3 doTonemapping(vec3 color) { vec3 ICtCp = toICtCp * lms_PQ; float luminance = singlePqToLinear(ICtCp.r) * 10000.0; - // if the reference is too close to the maximum luminance, reduce it to get up to 50% headroom float inputRange = maxTonemappingLuminance / destinationReferenceLuminance; float outputRange = maxDestinationLuminance / destinationReferenceLuminance; - float addedRange = min(inputRange, clamp(1.5 / outputRange, 1.0, 1.5)); - float outputReferenceLuminance = destinationReferenceLuminance / addedRange; + // how much dynamic range we need to decently present the content + float minDecentRange = min(inputRange, 1.5); + // if the output doesn't provide enough HDR headroom for the tone mapper to do a good job, dim the image to create some + float referenceDimming = 1.0 / clamp(outputRange / minDecentRange, 1.0, minDecentRange); + float outputReferenceLuminance = destinationReferenceLuminance * referenceDimming; // keep it linear up to the reference luminance - float low = min(luminance / addedRange, outputReferenceLuminance); + float low = min(luminance * referenceDimming, outputReferenceLuminance); // and apply a nonlinear curve above, to reduce the luminance without completely removing differences float relativeHighlight = clamp((luminance / destinationReferenceLuminance - 1.0) / (inputRange - 1.0), 0.0, 1.0); const float e = 2.718281828459045;