From patchwork Mon Jun 15 14:05:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 26882 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id E4E3EC324C for ; Mon, 15 Jun 2026 14:06:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 379D4623F4; Mon, 15 Jun 2026 16:05:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="IYZucAFK"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 42789623E1 for ; Mon, 15 Jun 2026 16:05:48 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7B94B1E1B; Mon, 15 Jun 2026 16:05:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532315; bh=Ll5r+4v/REyWSytwnhKQKhR6P2gts8C2571acQh/v1s=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=IYZucAFKDnPBrP+Dp2b2kBXUNy/1HWW4ebY3XHmYOa18sQkinxPbPXIfOBy32jNGj 5jkRbURQb0cIwEqWb7J67sRDSAkNeCa9n+rByoNvYbj5GqI02UYXw9y+jPnuWfzn4Y YAMCE+duGyzPd+YUZt4sXGmb8H1gzlXJCAX44/fE= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:30 +0200 Subject: [PATCH 05/11] ipa: libipa: Add CcmAlgorithm to libipa MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-5-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=12961; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=Ll5r+4v/REyWSytwnhKQKhR6P2gts8C2571acQh/v1s=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa5VeUv7idtLyoxzUuIZU6RUOwHU14UvL7zw Xu+Xd45vrOJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuQAKCRByNAaPFqFW PHeGD/9c3/rI2F8j2PKZa9ohpirm6zJuugWz09Y0Z0x4G+UHv1vknl5QN18KLZVtBRnaG+QZFjW dXpM7XWEnFOHNeFWyQYLLVW6s03ONkTXhEglH2f+dyg6i/3p1lftCME8PQVRoALp6+/hAFXouVr Tq5gn1EUUWIbTG7ghtg4DAFwljvQ9LGPopAeluIakyDmwEoVjjUziyhk1Fu5RjlZ7Lwi9qOmk8n m8zxBKBSKEGzFMpL5RRANnVkc73IckKYTJhfjwya3KD8Tdrg/PHcBLIe2YAPfHmGLz9QBP6nYrH S1h2XBg6enCLQKBj/CDrnlLI6wgxib2clUhNGUmeMSy3zh7QlT9YFz9MxWjWOd16AaujJQXSlNh +kQSbEvQCkbDc9+3K4Ys8jHHuE6ketAWlkWZoRFxXjrOtNemD8pQgHcuuyNJ6NG+k1FpHBoVeAN 9N68sPmkqRSjgCVtUTXg4UwJMTlvOMRV7KIs5LMtfAbgAlyxG0XRWVauKEmNXDG+A02NW1JNhVc HenGAQeFx7VuO7H2r7x7Sm5vlSaVpHH8XAca3L3Vy6qios0ytqK5Yp1hZUl8M+dyMtx2IxckZzo F/rlBX2I1ndq9L6TIBb37lApfE4uncP8C+VTnr4aTVtsbHBDgwukZfb++OhuRyCN2f8RHX5kAB8 0yKGrAcFT/j9mWQ== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a CcmAlgorithm to libipa. The CcmAlgorithm performs interpolation of the colour correction matrices as loaded from tuning file on a colour temperature. The implementation is based on the existing RkISP1 CCM algorihtm. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/ccm.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/ccm.h | 87 +++++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 362 insertions(+) diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp new file mode 100644 index 000000000000..278f8534e899 --- /dev/null +++ b/src/ipa/libipa/ccm.cpp @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA CCM algorithm + */ + +#include "ccm.h" + +/** + * \file ccm.h + * \brief libipa ccm (Colour Correction Matrix) algorithm + */ + +namespace libcamera { + +namespace ipa { + +LOG_DEFINE_CATEGORY(Ccm) + +namespace ccm { + +/** + * \struct ActiveState + * \brief Active ccm state + * + * \var ActiveState::manual + * \brief The most recent manually requested ccm state + * + * \var ActiveState::automatic + * \brief The most recent automatically calculated ccm state + */ + +/** + * \struct ActiveState::CcmState + * \brief Ccm coefficients and offsets + * + * \var ActiveState::CcmState::ccm + * \brief Matrix of 3x3 ccm coefficients + * + * \var ActiveState::CcmState::offsets + * \brief Vector of RGB ccm offsets + */ + +/** + * \struct FrameContext + * \brief Per-frame ccm state + * + * \var FrameContext::ccm + * \brief Matrix of 3x3 ccm coefficients + * + * \var FrameContext::offsets + * \brief Vector of RGB ccm offsets + */ + +} /* namespace ccm */ + +/** + * \class CcmAlgorithmBase + * \brief Base class for CcmAlgorithm for non-templated functions implementation + * + * Base class for CcmAlgorithm where non-templated functions are implemented. + * IPA implementations shall use CcmAlgorithm and not this class. + */ + +/** + * \brief Initialize the algorithm with the given tuning data + * \param[in] tuningData The tuning data to use for the algorithm + * + * Parse \a tuningData to initialize the ccm algorithm and register controls. + * IPA modules are expected to call this function as part of their + * implementation of Algorithm::init(). + * + * \return 0 on success, a negative error code otherwise + */ +int CcmAlgorithmBase::init(const ValueNode &tuningData) +{ + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); + if (ret < 0) { + LOG(Ccm, Warning) + << "Failed to parse 'ccm' " + << "parameter from tuning file; falling back to unit matrix"; + ccm_.setData({ { 0, Matrix::identity() } }); + } + + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); + if (ret < 0) { + LOG(Ccm, Warning) + << "Failed to parse 'offsets' " + << "parameter from tuning file; falling back to zero offsets"; + + offsets_.setData({ { 0, Matrix({ 0, 0, 0 }) } }); + } + + return 0; +} + +/** + * \brief Configure the ccm algorithm + * \param[in] state The ccm active state + * \param[in] temperatureK The colour temperature in Kelvin + * + * Configure the ccm algorithm by initializing the manual and automatic + * states in \a state by interpolating the default colour correction matrix + * with the given colour temperature \a temperatureK. + * + * \return 0 if successful, an error code otherwise + */ +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) +{ + state.manual.ccm = ccm_.getInterpolated(temperatureK); + state.manual.offsets = offsets_.getInterpolated(temperatureK); + state.automatic.ccm = ccm_.getInterpolated(temperatureK); + state.automatic.offsets = offsets_.getInterpolated(temperatureK); + + return 0; +} + +/** + * \brief Queue a Request to the ccm algorithm + * \param[in] state The ccm active state + * \param[in] context The ccm frame context + * \param[in] controls The list of controls part of the Request + * + * Queue a new Request to the ccm algorithm and store the manual colour + * correction matrix and temperature in \a frameContext. + * + * The currently handled controls are: + * - controls::ColourTemperature + * - controls::ColourCorrectionMatrix + * + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is + * stored in \a state and \a context. + * + * When controls::ColourTemperature is passed in, the matrices loaded from + * configuration file are interpolated with the give temperature and the result + * is stored in \a state and \a context. + * + * If the IPA is running in manual mode, the IPA ccm algorithm implementations + * can use the matrix coefficients and offsets directly from \a context after + * calling this function to program the HW ccm engine, without calling prepare(). + */ +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, + ccm::FrameContext &context, + const ControlList &controls) +{ + const auto &colourTemperature = controls.get(controls::ColourTemperature); + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); + if (ccmMatrix) { + state.manual.ccm = Matrix(*ccmMatrix); + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " + << state.manual.ccm; + } else if (colourTemperature) { + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " + << state.manual.ccm; + } + + context.ccm = state.manual.ccm; + context.offsets = state.manual.offsets; +} + +/** + * \brief Calculate the matrix coefficients for a colour temperature + * \param[in] state The ccm active state + * \param[in] context The ccm frame context + * \param[in] frame The frame number + * \param[in] temperatureK The colour temperature in Kelvin + * + * Interpolate the colour correction matrices as loaded from configuration file + * for colour temperature \a temperatureK. + * + * The function shall only be called if the IPA algorithm is running in auto + * mode. If running in manual mode the application supplied correction matrix is + * stored in \a frameContext at queueRequest() time. + */ +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, + ccm::FrameContext &context, + unsigned int frame, unsigned int temperatureK) +{ + if (frame > 0 && temperatureK == ct_) { + context.ccm = state.automatic.ccm; + return; + } + + ct_ = temperatureK; + context.ccm = ccm_.getInterpolated(ct_); + context.offsets = offsets_.getInterpolated(ct_); + + state.automatic.ccm = context.ccm; + state.automatic.offsets = context.offsets; +} + +/** + * \brief Populate metadata with the latest correction matrix coefficients + * \param[in] context The ccm frame context + * \param[out] metadata The metadata list + */ +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) +{ + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); +} + +/** + * \var CcmAlgorithmBase::coeffMin_ + * \brief The minimum supported coefficients value + * + * Minimum coefficient value used to clamp the ccm algorithm calculation results + * in the range supported by the platform ccm engine. + * + * The min and max gain values are initialized by CcmAlgorithm::init(). + */ + +/** + * \var CcmAlgorithmBase::coeffMax_ + * \brief The maximum supported coefficients value + * + * Maximum coefficient value used to clamp the ccm algorithm calculation results + * in the range supported by the platform ccm engine. + * + * The min and max gain values are initialized by CcmAlgorithm::init(). + */ + +/** + * \class CcmAlgorithm + * \brief The libipa ccm algorithm + * \tparam Q The fixedpoint register representation of the colour correction + * coefficients + * + * Implement the ccm algorithm for libipa. + * + * The CcmAlgorithm class implement an interface similar in spirit to the one + * of the Algorithm class. IPA modules are expected to store an instance of + * CcmAlgorithm as class member, template it with the ccm coefficients register + * representation and call its function in their implementations of the + * Algorithm interface. + * + * The CcmAlgorithm class provides an init() function where tuning data are + * parsed and the per-colour temperature correction matrices are loaded from + * the tuning file. + * + * CcmAlgorithm supports both automatic and manual colour correction operations, + * but doesn't offer a way to select one of them. Enabling or disabling + * automatic ccm operations usually goes through the Awb algorithm + * enable/disable as the two algorithms should work with the same mode. + * + * When the Awb algorithm runs in manual mode a custom colour correction matrix + * or a custom colour temperature can be supplied to the ccm algorithm at + * queueRequest() time. If the Request contains a color correction matrix + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in + * the FrameContext and the IPA module can immediately use them and doesn't need + * to call process(). If a custom colour temperature is provided + * (controls::ColourTemperature) then the matrices loaded from configuration are + * interpolated with it and the result is saved in the FrameContext. In this + * case as well IPA modules can use the result immediately and should avoid + * calling process(). + * + * When the Awb algorithm runs in automatic mode instead, it estimates the scene + * colour temperature. The estimated colour temperature shall be passed to + * process(), where it is used to interpolate the matrices loaded from the + * tuning file. The resulting coefficients are stored in the FrameContext for + * the IPA algorithm to use them to program their ccm engine registers. + */ + +/** + * \fn CcmAlgorithm::init() + * \param[in] controls The info map of the IPA controls + * \copydoc CcmAlgorithmBase::init() + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h new file mode 100644 index 000000000000..26ea0789481d --- /dev/null +++ b/src/ipa/libipa/ccm.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA CCM algorithm + */ + +#pragma once + +#include +#include + +#include "libcamera/internal/matrix.h" + +#include "fixedpoint.h" +#include "interpolator.h" + +namespace libcamera { + +namespace ipa { + +namespace ccm { + +struct ActiveState { + struct CcmState { + Matrix ccm; + Matrix offsets; + }; + + struct CcmState manual; + struct CcmState automatic; +}; + +struct FrameContext { + Matrix ccm; + Matrix offsets; +}; + +} /* namespace ccm */ + +class CcmAlgorithmBase +{ +public: + int init(const ValueNode &tuningData); + int configure(ccm::ActiveState &state, unsigned int temperatureK); + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, + const ControlList &controls); + + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, + unsigned int frame, unsigned int temperatureK); + void process(ccm::FrameContext &context, ControlList &metadata); + +protected: + float coeffMin_; + float coeffMax_; + +private: + unsigned int ct_; + Interpolator> ccm_; + Interpolator> offsets_; +}; + +template +class CcmAlgorithm : public CcmAlgorithmBase +{ +public: + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) + { + int ret = CcmAlgorithmBase::init(tuningData); + if (ret) + return ret; + + coeffMin_ = Q::TraitsType::min; + coeffMax_ = Q::TraitsType::max; + + controls[&controls::ColourCorrectionMatrix] = + ControlInfo(ControlValue(coeffMin_), + ControlValue(coeffMax_), + ControlValue(Matrix::identity().data())); + + return 0; + } +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 963c5ee73063..edf8eabd8b78 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -7,6 +7,7 @@ libipa_headers = files([ 'awb_grey.h', 'awb.h', 'camera_sensor_helper.h', + 'ccm.h', 'colours.h', 'exposure_mode_helper.h', 'fc_queue.h', @@ -28,6 +29,7 @@ libipa_sources = files([ 'awb_grey.cpp', 'awb.cpp', 'camera_sensor_helper.cpp', + 'ccm.cpp', 'colours.cpp', 'exposure_mode_helper.cpp', 'fc_queue.cpp',