core/colorspace: implement black point compensation

This commit is contained in:
Xaver Hugl 2024-08-12 02:25:29 +02:00
parent cd371d8618
commit 0d0135e237
6 changed files with 39 additions and 11 deletions

View file

@ -11,6 +11,14 @@
namespace KWin
{
ValueRange ValueRange::operator*(double mult) const
{
return ValueRange{
.min = min * mult,
.max = max * mult,
};
}
ColorPipeline ColorPipeline::create(const ColorDescription &from, const ColorDescription &to, RenderingIntent intent)
{
const auto range1 = ValueRange(from.minLuminance(), from.maxHdrLuminance().value_or(from.referenceLuminance()));
@ -19,11 +27,10 @@ ColorPipeline ColorPipeline::create(const ColorDescription &from, const ColorDes
.max = from.transferFunction().nitsToEncoded(range1.max),
});
ret.addTransferFunction(from.transferFunction());
ret.addMultiplier(to.referenceLuminance() / from.referenceLuminance());
// FIXME this assumes that the range stays the same with matrix multiplication
// that's not necessarily true, and figuring out the actual range could be complicated..
ret.addMatrix(from.toOther(to, intent), ret.currentOutputRange());
ret.addMatrix(from.toOther(to, intent), ret.currentOutputRange() * (to.referenceLuminance() / from.referenceLuminance()));
ret.addInverseTransferFunction(to.transferFunction());
return ret;

View file

@ -20,6 +20,7 @@ public:
double max = 1;
bool operator==(const ValueRange &) const = default;
ValueRange operator*(double mult) const;
};
class KWIN_EXPORT ColorTransferFunction

View file

@ -326,15 +326,36 @@ std::optional<double> ColorDescription::maxHdrLuminance() const
QMatrix4x4 ColorDescription::toOther(const ColorDescription &other, RenderingIntent intent) const
{
QMatrix4x4 luminanceBefore;
QMatrix4x4 luminanceAfter;
if (intent == RenderingIntent::Perceptual || intent == RenderingIntent::RelativeColorimetricWithBPC) {
// add black point compensation: black and reference white from the source color space
// should both be mapped to black and reference white in the destination color space
// before color conversions, map [src min, src ref] to [0, 1]
luminanceBefore.scale(1.0 / (referenceLuminance() - minLuminance()));
luminanceBefore.translate(-minLuminance(), -minLuminance(), -minLuminance());
// afterwards, map [0, 1] again to [dst min, dst ref]
luminanceAfter.translate(other.minLuminance(), other.minLuminance(), other.minLuminance());
luminanceAfter.scale(other.referenceLuminance() - other.minLuminance());
} else {
// map only the reference luminance
luminanceBefore.scale(other.referenceLuminance() / referenceLuminance());
}
switch (intent) {
case RenderingIntent::Perceptual: {
const Colorimetry &srcContainer = containerColorimetry() == NamedColorimetry::BT709 ? other.sdrColorimetry() : containerColorimetry();
return other.containerColorimetry().fromXYZ() * Colorimetry::chromaticAdaptationMatrix(srcContainer.white(), other.containerColorimetry().white()) * srcContainer.toXYZ();
return luminanceAfter * other.containerColorimetry().fromXYZ() * Colorimetry::chromaticAdaptationMatrix(srcContainer.white(), other.containerColorimetry().white()) * srcContainer.toXYZ() * luminanceBefore;
}
case RenderingIntent::RelativeColorimetric: {
return luminanceAfter * other.containerColorimetry().fromXYZ() * Colorimetry::chromaticAdaptationMatrix(containerColorimetry().white(), other.containerColorimetry().white()) * containerColorimetry().toXYZ() * luminanceBefore;
}
case RenderingIntent::RelativeColorimetricWithBPC: {
return luminanceAfter * other.containerColorimetry().fromXYZ() * Colorimetry::chromaticAdaptationMatrix(containerColorimetry().white(), other.containerColorimetry().white()) * containerColorimetry().toXYZ() * luminanceBefore;
}
case RenderingIntent::AbsoluteColorimetric: {
return luminanceAfter * other.containerColorimetry().fromXYZ() * containerColorimetry().toXYZ() * luminanceBefore;
}
case RenderingIntent::RelativeColorimetric:
return other.containerColorimetry().fromXYZ() * Colorimetry::chromaticAdaptationMatrix(containerColorimetry().white(), other.containerColorimetry().white()) * containerColorimetry().toXYZ();
case RenderingIntent::AbsoluteColorimetric:
return other.containerColorimetry().fromXYZ() * containerColorimetry().toXYZ();
}
Q_UNREACHABLE();
}
@ -342,7 +363,6 @@ QMatrix4x4 ColorDescription::toOther(const ColorDescription &other, RenderingInt
QVector3D ColorDescription::mapTo(QVector3D rgb, const ColorDescription &dst, RenderingIntent intent) const
{
rgb = m_transferFunction.encodedToNits(rgb);
rgb *= dst.referenceLuminance() / m_referenceLuminance;
rgb = toOther(dst, intent) * rgb;
return dst.transferFunction().nitsToEncoded(rgb);
}

View file

@ -27,7 +27,7 @@ enum class RenderingIntent {
/* colorimetric mapping between color spaces, without whitepoint adaptation */
AbsoluteColorimetric,
/* colorimetric mapping between color spaces, with whitepoint adaptation and black point compensation */
// TODO RelativeColorimetricWithBPC,
RelativeColorimetricWithBPC,
};
enum class NamedColorimetry {

View file

@ -94,7 +94,6 @@ vec4 encodingToNits(vec4 color, int sourceTransferFunction, float luminanceOffse
vec4 sourceEncodingToNitsInDestinationColorspace(vec4 color) {
color = encodingToNits(color, sourceNamedTransferFunction, sourceTransferFunctionParams.x, sourceTransferFunctionParams.y);
color.rgb = color.rgb * (destinationReferenceLuminance / sourceReferenceLuminance);
color.rgb = (colorimetryTransform * vec4(color.rgb, 1.0)).rgb;
return vec4(doTonemapping(color.rgb, maxDestinationLuminance), color.a);
}

View file

@ -46,7 +46,8 @@ void XXColorManagerV4::xx_color_manager_v4_bind_resource(Resource *resource)
send_supported_intent(resource->handle, render_intent::render_intent_perceptual);
send_supported_intent(resource->handle, render_intent::render_intent_relative);
send_supported_intent(resource->handle, render_intent::render_intent_absolute);
// TODO implement saturation and relative bpc intents
send_supported_intent(resource->handle, render_intent::render_intent_relative_bpc);
// TODO implement saturation intent
}
void XXColorManagerV4::xx_color_manager_v4_destroy(Resource *resource)