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
This commit is contained in:
Xaver Hugl 2024-08-30 19:04:05 +02:00
parent 587afb3076
commit eae1f51304
3 changed files with 21 additions and 16 deletions

View file

@ -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,7 +249,7 @@ 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),
.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);

View file

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

View file

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