From 0d0135e2371f643ca99e186072eb470216ee948c Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Mon, 12 Aug 2024 02:25:29 +0200 Subject: [PATCH] core/colorspace: implement black point compensation --- src/core/colorpipeline.cpp | 11 +++++++-- src/core/colorpipeline.h | 1 + src/core/colorspace.cpp | 32 ++++++++++++++++++++++----- src/core/colorspace.h | 2 +- src/opengl/colormanagement.glsl | 1 - src/wayland/xx_colormanagement_v4.cpp | 3 ++- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/core/colorpipeline.cpp b/src/core/colorpipeline.cpp index 07d165cedb..b175e0ec53 100644 --- a/src/core/colorpipeline.cpp +++ b/src/core/colorpipeline.cpp @@ -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; diff --git a/src/core/colorpipeline.h b/src/core/colorpipeline.h index 651bad8a3f..bb55321912 100644 --- a/src/core/colorpipeline.h +++ b/src/core/colorpipeline.h @@ -20,6 +20,7 @@ public: double max = 1; bool operator==(const ValueRange &) const = default; + ValueRange operator*(double mult) const; }; class KWIN_EXPORT ColorTransferFunction diff --git a/src/core/colorspace.cpp b/src/core/colorspace.cpp index 8f936fe157..58cab0a018 100644 --- a/src/core/colorspace.cpp +++ b/src/core/colorspace.cpp @@ -326,15 +326,36 @@ std::optional 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); } diff --git a/src/core/colorspace.h b/src/core/colorspace.h index 1942f3e6e5..ef3a88c0d6 100644 --- a/src/core/colorspace.h +++ b/src/core/colorspace.h @@ -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 { diff --git a/src/opengl/colormanagement.glsl b/src/opengl/colormanagement.glsl index 9d72cc1651..26f05f62ab 100644 --- a/src/opengl/colormanagement.glsl +++ b/src/opengl/colormanagement.glsl @@ -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); } diff --git a/src/wayland/xx_colormanagement_v4.cpp b/src/wayland/xx_colormanagement_v4.cpp index e49c5aa316..1a677beecc 100644 --- a/src/wayland/xx_colormanagement_v4.cpp +++ b/src/wayland/xx_colormanagement_v4.cpp @@ -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)