[{"id":37955,"web_url":"https://patchwork.libcamera.org/comment/37955/","msgid":"<fa08800e-6a24-4d40-875d-758d358a52c5@collabora.com>","date":"2026-01-26T15:57:53","subject":"Re: [PATCH v4 07/15] libcamera: ipa: simple: Separate saturation\n\tfrom CCM","submitter":{"id":140,"url":"https://patchwork.libcamera.org/api/people/140/","name":"Robert Mader","email":"robert.mader@collabora.com"},"content":"Reviewed-by: Robert Mader <robert.mader@collabora.com>\n\nOn 22.01.26 17:19, Milan Zamazal wrote:\n> Saturation adjustments are implemented using matrix operations.  They\n> are currently applied to the colour correction matrix.  Let's move them\n> to a newly introduced separate \"Adjust\" algorithm.\n>\n> This separation has the following advantages:\n>\n> - It allows disabling general colour adjustments algorithms without\n>    disabling the CCM algorithm.\n>\n> - It keeps the CCM separated from other corrections.\n>\n> - It's generally cleaner.\n>\n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n> ---\n>   src/ipa/simple/algorithms/adjust.cpp  | 106 ++++++++++++++++++++++++++\n>   src/ipa/simple/algorithms/adjust.h    |  52 +++++++++++++\n>   src/ipa/simple/algorithms/ccm.cpp     |  60 +--------------\n>   src/ipa/simple/algorithms/ccm.h       |   9 ---\n>   src/ipa/simple/algorithms/meson.build |   1 +\n>   src/ipa/simple/data/uncalibrated.yaml |   1 +\n>   6 files changed, 164 insertions(+), 65 deletions(-)\n>   create mode 100644 src/ipa/simple/algorithms/adjust.cpp\n>   create mode 100644 src/ipa/simple/algorithms/adjust.h\n>\n> diff --git a/src/ipa/simple/algorithms/adjust.cpp b/src/ipa/simple/algorithms/adjust.cpp\n> new file mode 100644\n> index 000000000..8ce0631e4\n> --- /dev/null\n> +++ b/src/ipa/simple/algorithms/adjust.cpp\n> @@ -0,0 +1,106 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Ideas On Board\n> + * Copyright (C) 2024-2025, Red Hat Inc.\n> + *\n> + * Common image adjustments\n> + */\n> +\n> +#include \"adjust.h\"\n> +\n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/utils.h>\n> +\n> +#include <libcamera/control_ids.h>\n> +\n> +#include \"libcamera/internal/matrix.h\"\n> +\n> +namespace libcamera {\n> +\n> +namespace ipa::soft::algorithms {\n> +\n> +LOG_DEFINE_CATEGORY(IPASoftAdjust)\n> +\n> +int Adjust::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)\n> +{\n> +\tif (context.ccmEnabled)\n> +\t\tcontext.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);\n> +\treturn 0;\n> +}\n> +\n> +int Adjust::configure(IPAContext &context,\n> +\t\t      [[maybe_unused]] const IPAConfigInfo &configInfo)\n> +{\n> +\tcontext.activeState.knobs.saturation = std::optional<double>();\n> +\n> +\treturn 0;\n> +}\n> +\n> +void Adjust::queueRequest(typename Module::Context &context,\n> +\t\t\t  [[maybe_unused]] const uint32_t frame,\n> +\t\t\t  [[maybe_unused]] typename Module::FrameContext &frameContext,\n> +\t\t\t  const ControlList &controls)\n> +{\n> +\tconst auto &saturation = controls.get(controls::Saturation);\n> +\tif (saturation.has_value()) {\n> +\t\tcontext.activeState.knobs.saturation = saturation;\n> +\t\tLOG(IPASoftAdjust, Debug) << \"Setting saturation to \" << saturation.value();\n> +\t}\n> +}\n> +\n> +void Adjust::applySaturation(Matrix<float, 3, 3> &matrix, float saturation)\n> +{\n> +\t/* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */\n> +\tconst Matrix<float, 3, 3> rgb2ycbcr{\n> +\t\t{ 0.256788235294, 0.504129411765, 0.0979058823529,\n> +\t\t  -0.148223529412, -0.290992156863, 0.439215686275,\n> +\t\t  0.439215686275, -0.367788235294, -0.0714274509804 }\n> +\t};\n> +\tconst Matrix<float, 3, 3> ycbcr2rgb{\n> +\t\t{ 1.16438356164, 0, 1.59602678571,\n> +\t\t  1.16438356164, -0.391762290094, -0.812967647235,\n> +\t\t  1.16438356164, 2.01723214285, 0 }\n> +\t};\n> +\tconst Matrix<float, 3, 3> saturationMatrix{\n> +\t\t{ 1, 0, 0,\n> +\t\t  0, saturation, 0,\n> +\t\t  0, 0, saturation }\n> +\t};\n> +\tmatrix =\n> +\t\tycbcr2rgb * saturationMatrix * rgb2ycbcr * matrix;\n> +}\n> +\n> +void Adjust::prepare(IPAContext &context,\n> +\t\t     [[maybe_unused]] const uint32_t frame,\n> +\t\t     IPAFrameContext &frameContext,\n> +\t\t     [[maybe_unused]] DebayerParams *params)\n> +{\n> +\tif (!context.ccmEnabled)\n> +\t\treturn;\n> +\n> +\tauto &saturation = context.activeState.knobs.saturation;\n> +\tframeContext.saturation = saturation;\n> +\tif (saturation)\n> +\t\tapplySaturation(context.activeState.combinedMatrix, saturation.value());\n> +\n> +\tif (saturation != lastSaturation_) {\n> +\t\tcontext.activeState.matrixChanged = true;\n> +\t\tlastSaturation_ = saturation;\n> +\t}\n> +}\n> +\n> +void Adjust::process([[maybe_unused]] IPAContext &context,\n> +\t\t     [[maybe_unused]] const uint32_t frame,\n> +\t\t     IPAFrameContext &frameContext,\n> +\t\t     [[maybe_unused]] const SwIspStats *stats,\n> +\t\t     ControlList &metadata)\n> +{\n> +\tconst auto &saturation = frameContext.saturation;\n> +\tmetadata.set(controls::Saturation, saturation.value_or(1.0));\n> +}\n> +\n> +REGISTER_IPA_ALGORITHM(Adjust, \"Adjust\")\n> +\n> +} /* namespace ipa::soft::algorithms */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/simple/algorithms/adjust.h b/src/ipa/simple/algorithms/adjust.h\n> new file mode 100644\n> index 000000000..c4baa2503\n> --- /dev/null\n> +++ b/src/ipa/simple/algorithms/adjust.h\n> @@ -0,0 +1,52 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024-2025, Red Hat Inc.\n> + *\n> + * Color correction matrix\n> + */\n> +\n> +#pragma once\n> +\n> +#include <optional>\n> +\n> +#include \"libcamera/internal/matrix.h\"\n> +\n> +#include <libipa/interpolator.h>\n> +\n> +#include \"algorithm.h\"\n> +\n> +namespace libcamera {\n> +\n> +namespace ipa::soft::algorithms {\n> +\n> +class Adjust : public Algorithm\n> +{\n> +public:\n> +\tAdjust() = default;\n> +\t~Adjust() = default;\n> +\n> +\tint init(IPAContext &context, const YamlObject &tuningData) override;\n> +\tint configure(IPAContext &context,\n> +\t\t      const IPAConfigInfo &configInfo) override;\n> +\tvoid queueRequest(typename Module::Context &context,\n> +\t\t\t  const uint32_t frame,\n> +\t\t\t  typename Module::FrameContext &frameContext,\n> +\t\t\t  const ControlList &controls) override;\n> +\tvoid prepare(IPAContext &context,\n> +\t\t     const uint32_t frame,\n> +\t\t     IPAFrameContext &frameContext,\n> +\t\t     DebayerParams *params) override;\n> +\tvoid process(IPAContext &context, const uint32_t frame,\n> +\t\t     IPAFrameContext &frameContext,\n> +\t\t     const SwIspStats *stats,\n> +\t\t     ControlList &metadata) override;\n> +\n> +private:\n> +\tvoid applySaturation(Matrix<float, 3, 3> &ccm, float saturation);\n> +\n> +\tstd::optional<float> lastSaturation_;\n> +};\n> +\n> +} /* namespace ipa::soft::algorithms */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\n> index 85643645b..5576a301f 100644\n> --- a/src/ipa/simple/algorithms/ccm.cpp\n> +++ b/src/ipa/simple/algorithms/ccm.cpp\n> @@ -1,9 +1,9 @@\n>   /* SPDX-License-Identifier: LGPL-2.1-or-later */\n>   /*\n>    * Copyright (C) 2024, Ideas On Board\n> - * Copyright (C) 2024-2025, Red Hat Inc.\n> + * Copyright (C) 2024-2026, Red Hat Inc.\n>    *\n> - * Color correction matrix + saturation\n> + * Color correction matrix\n>    */\n>   \n>   #include \"ccm.h\"\n> @@ -37,74 +37,25 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData\n>   \t}\n>   \n>   \tcontext.ccmEnabled = true;\n> -\tcontext.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);\n>   \n>   \treturn 0;\n>   }\n>   \n> -int Ccm::configure(IPAContext &context,\n> -\t\t   [[maybe_unused]] const IPAConfigInfo &configInfo)\n> -{\n> -\tcontext.activeState.knobs.saturation = std::optional<double>();\n> -\n> -\treturn 0;\n> -}\n> -\n> -void Ccm::queueRequest(typename Module::Context &context,\n> -\t\t       [[maybe_unused]] const uint32_t frame,\n> -\t\t       [[maybe_unused]] typename Module::FrameContext &frameContext,\n> -\t\t       const ControlList &controls)\n> -{\n> -\tconst auto &saturation = controls.get(controls::Saturation);\n> -\tif (saturation.has_value()) {\n> -\t\tcontext.activeState.knobs.saturation = saturation;\n> -\t\tLOG(IPASoftCcm, Debug) << \"Setting saturation to \" << saturation.value();\n> -\t}\n> -}\n> -\n> -void Ccm::applySaturation(Matrix<float, 3, 3> &ccm, float saturation)\n> -{\n> -\t/* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */\n> -\tconst Matrix<float, 3, 3> rgb2ycbcr{\n> -\t\t{ 0.256788235294, 0.504129411765, 0.0979058823529,\n> -\t\t  -0.148223529412, -0.290992156863, 0.439215686275,\n> -\t\t  0.439215686275, -0.367788235294, -0.0714274509804 }\n> -\t};\n> -\tconst Matrix<float, 3, 3> ycbcr2rgb{\n> -\t\t{ 1.16438356164, 0, 1.59602678571,\n> -\t\t  1.16438356164, -0.391762290094, -0.812967647235,\n> -\t\t  1.16438356164, 2.01723214285, 0 }\n> -\t};\n> -\tconst Matrix<float, 3, 3> saturationMatrix{\n> -\t\t{ 1, 0, 0,\n> -\t\t  0, saturation, 0,\n> -\t\t  0, 0, saturation }\n> -\t};\n> -\tccm = ycbcr2rgb * saturationMatrix * rgb2ycbcr * ccm;\n> -}\n> -\n>   void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>   \t\t  IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params)\n>   {\n> -\tauto &saturation = context.activeState.knobs.saturation;\n> -\n>   \tconst unsigned int ct = context.activeState.awb.temperatureK;\n>   \n> -\t/* Change CCM only on saturation or bigger temperature changes. */\n> +\t/* Change CCM only on bigger temperature changes. */\n>   \tif (!currentCcm_ ||\n> -\t    utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold ||\n> -\t    saturation != lastSaturation_) {\n> +\t    utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) {\n>   \t\tcurrentCcm_ = ccm_.getInterpolated(ct);\n> -\t\tif (saturation)\n> -\t\t\tapplySaturation(currentCcm_.value(), saturation.value());\n>   \t\tlastCt_ = ct;\n> -\t\tlastSaturation_ = saturation;\n>   \t\tcontext.activeState.matrixChanged = true;\n>   \t}\n>   \n>   \tcontext.activeState.combinedMatrix =\n>   \t\tcurrentCcm_.value() * context.activeState.combinedMatrix;\n> -\tframeContext.saturation = saturation;\n>   \tframeContext.ccm = currentCcm_.value();\n>   }\n>   \n> @@ -115,9 +66,6 @@ void Ccm::process([[maybe_unused]] IPAContext &context,\n>   \t\t  ControlList &metadata)\n>   {\n>   \tmetadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.data());\n> -\n> -\tconst auto &saturation = frameContext.saturation;\n> -\tmetadata.set(controls::Saturation, saturation.value_or(1.0));\n>   }\n>   \n>   REGISTER_IPA_ALGORITHM(Ccm, \"Ccm\")\n> diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h\n> index 867a680c3..62009a375 100644\n> --- a/src/ipa/simple/algorithms/ccm.h\n> +++ b/src/ipa/simple/algorithms/ccm.h\n> @@ -26,12 +26,6 @@ public:\n>   \t~Ccm() = default;\n>   \n>   \tint init(IPAContext &context, const YamlObject &tuningData) override;\n> -\tint configure(IPAContext &context,\n> -\t\t      const IPAConfigInfo &configInfo) override;\n> -\tvoid queueRequest(typename Module::Context &context,\n> -\t\t\t  const uint32_t frame,\n> -\t\t\t  typename Module::FrameContext &frameContext,\n> -\t\t\t  const ControlList &controls) override;\n>   \tvoid prepare(IPAContext &context,\n>   \t\t     const uint32_t frame,\n>   \t\t     IPAFrameContext &frameContext,\n> @@ -42,10 +36,7 @@ public:\n>   \t\t     ControlList &metadata) override;\n>   \n>   private:\n> -\tvoid applySaturation(Matrix<float, 3, 3> &ccm, float saturation);\n> -\n>   \tunsigned int lastCt_;\n> -\tstd::optional<float> lastSaturation_;\n>   \tInterpolator<Matrix<float, 3, 3>> ccm_;\n>   \tstd::optional<Matrix<float, 3, 3>> currentCcm_;\n>   };\n> diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build\n> index 2d0adb059..ebe9f20dd 100644\n> --- a/src/ipa/simple/algorithms/meson.build\n> +++ b/src/ipa/simple/algorithms/meson.build\n> @@ -1,6 +1,7 @@\n>   # SPDX-License-Identifier: CC0-1.0\n>   \n>   soft_simple_ipa_algorithms = files([\n> +    'adjust.cpp',\n>       'awb.cpp',\n>       'agc.cpp',\n>       'blc.cpp',\n> diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml\n> index 8b6df9afc..e389e0588 100644\n> --- a/src/ipa/simple/data/uncalibrated.yaml\n> +++ b/src/ipa/simple/data/uncalibrated.yaml\n> @@ -14,6 +14,7 @@ algorithms:\n>             ccm: [ 1, 0, 0,\n>                    0, 1, 0,\n>                    0, 0, 1]\n> +  - Adjust:\n>     - Lut:\n>     - Agc:\n>   ...","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 60937C3200\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 26 Jan 2026 15:58:01 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 04ABC61FC9;\n\tMon, 26 Jan 2026 16:58:01 +0100 (CET)","from sender4-op-o12.zoho.com (sender4-op-o12.zoho.com\n\t[136.143.188.12])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3138161FC4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 26 Jan 2026 16:57:59 +0100 (CET)","by mx.zohomail.com with SMTPS id 1769443075369187.58679722821694; \n\tMon, 26 Jan 2026 07:57:55 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=collabora.com\n\theader.i=robert.mader@collabora.com header.b=\"idcdSn7u\"; \n\tdkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1769443076; cv=none; \n\td=zohomail.com; s=zohoarc; \n\tb=csm3/pnn5c4iSasxn747Bc6Xk68S5+ew74j/SDNlraWSvVPsk/DXqL3+yOu9fp4dIU47fpbVOsdTcV7F+P+h6hmd1iNL3oUmA+QG7Gy9hIeUAwmiSYHmfOZUCXHhaKMCP0MPnIhQoywRSH1t49xqfG2MnBlt4gcxSiTtkdAdD7Y=","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; \n\ts=zohoarc; t=1769443076;\n\th=Content-Type:Content-Transfer-Encoding:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To:Cc;\n\tbh=BT50ZiGej8+c5PXSCGR/s2kll2pVg2ADyk+rRehNZsg=; \n\tb=IKyNz/HZnRU+pBg0ObGqnz+s80M15tJSnzR0Wh/7UTqK2yk8O4Px/dCW9bgoL1+7Rwmp3EjJ/sUxLUbCUC7bacs9HHwNK9UYDg0dPJEPcqH5ItRmB7WDcekLmzWWZBTNiu2an9Cb7VGE8KO7GLyVSDQfOtacFu1hjDUliGZJVYI=","ARC-Authentication-Results":"i=1; mx.zohomail.com;\n\tdkim=pass  header.i=collabora.com;\n\tspf=pass  smtp.mailfrom=robert.mader@collabora.com;\n\tdmarc=pass header.from=<robert.mader@collabora.com>","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1769443076;\n\ts=zohomail; d=collabora.com; i=robert.mader@collabora.com;\n\th=Message-ID:Date:Date:MIME-Version:Subject:Subject:To:To:References:From:From:In-Reply-To:Content-Type:Content-Transfer-Encoding:Message-Id:Reply-To:Cc;\n\tbh=BT50ZiGej8+c5PXSCGR/s2kll2pVg2ADyk+rRehNZsg=;\n\tb=idcdSn7usZSCALpCsK+KaCf3BeefCKUVfZnJzOHWSESe8qq5ZhGVyUtu/+IHAm0j\n\tX/4rFfK7DhmiaYOUQfjwi/OCreRGol23+8oOYdYRmZ/HicaRAjc5NAcppsDwTihm3Fc\n\t5jN2eoziUSJgfF8gvgKccmxnZVJkCoFcAPwYtdNA=","Message-ID":"<fa08800e-6a24-4d40-875d-758d358a52c5@collabora.com>","Date":"Mon, 26 Jan 2026 16:57:53 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v4 07/15] libcamera: ipa: simple: Separate saturation\n\tfrom CCM","To":"libcamera-devel@lists.libcamera.org","References":"<20260122161935.208562-1-mzamazal@redhat.com>\n\t<20260122161935.208562-8-mzamazal@redhat.com>","Content-Language":"en-US, de-DE","From":"Robert Mader <robert.mader@collabora.com>","In-Reply-To":"<20260122161935.208562-8-mzamazal@redhat.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]