From patchwork Wed Aug 5 16:12:43 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27624 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 14DB1C330F for ; Wed, 5 Aug 2026 16:13:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4124F68132; Wed, 5 Aug 2026 18:13:29 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="knVuCSZz"; 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 31D8E6811F for ; Wed, 5 Aug 2026 18:13:25 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 35953929; Wed, 5 Aug 2026 18:12:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946334; bh=RpUDcWqrtYtmnystDS0mFOGu34COlhfbnzDYcV9x7j0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=knVuCSZzWiE8yp32KE4vmbyig/DCCGAYb2FzC/xVVHG3reyIaUvIx/gk0B+ZUeN0t 0eQW+Sner1GqYcP95cYEN799Ajz1PJRSYm0O7LnfyKOegnoQK9l+4NsezVeD5gqZPj pr/ap5Zo4WySKD3V4AGyFLS4dp9d+VeIpybJ0wmk= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:43 +0200 Subject: [PATCH v7 01/32] ipa: libipa: awb: Reimplement AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-1-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally , Robert Mader X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=45702; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=RpUDcWqrtYtmnystDS0mFOGu34COlhfbnzDYcV9x7j0=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EZWi6tChsiieuWL9vEWOs31D+r7uVvfvf+B NLBW8reUcuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhGQAKCRByNAaPFqFW PAw/D/4yPMrlRtNIx/2/cI5mW1jeLg2LcgJ0ISmRpvpTBALL5bKMRhtvnNpLSDYW4wMczBpQ+4V czjeOrm8RYKo/0RiRLgSYiZ9L8ht1W/cDfLgiNxqbzqrKa0Zj8wFgNvzQ+KRkZ/GZZuya1f9ybS WWuGNb4kpx3pnA7RvBBG2dM8ARrWq8ylUgNc3EjkSg1YJQFJEXQFak/faaSwNRzXMI1DJoARBJ9 fS3D9iAFn4TQXImJBcXXzHyKn+X2roJMzHBXTYTjFNeLQv9nH+zhrqXE9+mv/U4NCcpWxL6P1Bx DCqYqZOj34kXoT/qZNSdcHGy5n+uARWHx9Pe7UbfDI6bAziK57aXyd0NF2ajevDIJGHxKC840Dc UOF9eYKduV0oszMhBVHJOG8MeGaAmlV5NwA/3VAchvq+sMY1lF/r7f1eI7Iqa/plkNiX4W6+lgA ViPdm4wKXg5GAFMxW0Q6sGQfRNqWDahuQNwqFUcqjVHYbCcVHKm48E1T9PJarlQumg8wPdUM++r /lBvVqTicGpQ27WKZYbOrkuVQl6spuaCco7AHw5kESyGVm475FpYbkmV3auAJgIUNRODh8DpDvl ZFSwkkNzNoWgT1wc7D8o9HMN3t+gEguqQ/jMLrwdGnOc3MCQIZRheGo17m4Lc+FcFlAVhNvdnL8 mkJ2d3HmI4csXuw== 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" The current implementation of AwbAlgorithm is the following one: AwbAlgorithm o---- RkISP1Awb ^ | |-----| | | Bayes Grey The platform-specific Awb implementation instantiate one of the Bayes or Grey Awb helper classes and both inherit from the AwbAlgorithm base class. The handling of libcamera controls is split between the base and the derived (mostly Bayes) class. We want instead the AwbAlgorithm to handle all-things-libcamera and let the Bayes and Grey classes implement the logic. In order to achieve that use composition also in the AwbAlgorithm class and introduce an AwbImplementation base class from which Bayes and Grey derive from. AwbImplementation o---- AwbAlgorithmBase ^ ^ | | |-----| AwbAlgorithm> o---- RkISP1Awb | | Bayes Grey The AwbAlgorithm<> class is instantiated with a Q template argument that represents the platform-specific register format which is used to initialize controls and clamp gains to the hardware limits. Signed-off-by: Jacopo Mondi Signed-off-by: Kieran Bingham Reviewed-by: Daniel Scally Tested-by: Daniel Scally Tested-by: Robert Mader --- v3->v4: - s/Awb|awb/AWB wherever appropriate - Make AwbStats pure virtual - Reduce comment changes to the bare minimum to avoid churn --- src/ipa/libipa/awb.cpp | 418 ++++++++++++++++++++++++++++++++++---- src/ipa/libipa/awb.h | 100 +++++++-- src/ipa/libipa/awb_bayes.cpp | 52 ++--- src/ipa/libipa/awb_bayes.h | 12 +- src/ipa/libipa/awb_grey.cpp | 16 +- src/ipa/libipa/awb_grey.h | 6 +- src/ipa/rkisp1/algorithms/awb.cpp | 191 +++-------------- src/ipa/rkisp1/algorithms/awb.h | 18 +- src/ipa/rkisp1/algorithms/ccm.cpp | 4 +- src/ipa/rkisp1/algorithms/lsc.cpp | 2 +- src/ipa/rkisp1/ipa_context.h | 29 +-- 11 files changed, 549 insertions(+), 299 deletions(-) diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp index 29a78069c52c..da835bec018d 100644 --- a/src/ipa/libipa/awb.cpp +++ b/src/ipa/libipa/awb.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2024 Ideas on Board Oy * - * Generic AWB algorithms + * libIPA AWB algorithms */ #include "awb.h" @@ -11,9 +11,16 @@ #include +#include "awb_bayes.h" +#include "awb_grey.h" + +constexpr int32_t kMinColourTemperature = 2500; +constexpr int32_t kMaxColourTemperature = 10000; +constexpr int32_t kDefaultColourTemperature = 5000; + /** * \file awb.h - * \brief Base classes for AWB algorithms + * \brief libipa AWB implementation */ namespace libcamera { @@ -22,30 +29,55 @@ LOG_DEFINE_CATEGORY(Awb) namespace ipa { +namespace awb { + /** - * \class AwbResult - * \brief The result of an AWB calculation + * \struct Context + * \brief AWB gains and colour temperature + * + * \var Context::gains + * \brief The white balance gains * - * This class holds the result of an auto white balance calculation. + * \var Context::colourTemperature + * \brief The colour temperature, in Kelvin */ /** - * \var AwbResult::gains - * \brief The calculated white balance gains + * \struct ActiveState + * \brief Active AWB state shared across frames + * + * \var ActiveState::manual + * \brief The most recent manually requested AWB state + * + * \var ActiveState::automatic + * \brief The most recent automatically calculated AWB state + * + * \var ActiveState::autoEnabled + * \brief True when automatic AWB is selected */ /** - * \var AwbResult::colourTemperature - * \brief The calculated colour temperature in Kelvin + * \struct FrameContext + * \brief Per-frame AWB state + * + * \var FrameContext::autoEnabled + * \brief True when automatic AWB is in use */ +} /* namespace awb */ + /** * \class AwbStats * \brief An abstraction class wrapping hardware-specific AWB statistics * - * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to - * implement this class to give the algorithm access to the hardware-specific - * statistics data. + * IPA modules using libIPA AwbAlgorithm class need to provide a derived + * implementation of this class to give the algorithm access to the + * hardware-specific statistics data. + */ + +/** + * AwbStat::~AwbStat + * \brief Virtual class destructor */ /** @@ -64,6 +96,17 @@ namespace ipa { * \return The computed error value */ +/** + * \fn AwbStats::valid() + * \brief Retrieve if the AWB statistics are valid + * + * If the colour mean values are too small, the AwbAlgorithm class doesn't have + * enough information to meaningfully calculate white-balance gains. Freeze the + * algorithm in that case. + * + * \return True if the AWB statistics are valid, false otherwise + */ + /** * \fn AwbStats::rgbMeans() * \brief Get RGB means of the statistics @@ -72,31 +115,63 @@ namespace ipa { * dimensionless and only the ratios are used for further calculations. This is * used by the simple grey world model to calculate the gains to apply. * + * \todo Make a requirement to return statistics in the [0, 1] range. + * * \return The RGB means */ /** - * \class AwbAlgorithm - * \brief A base class for auto white balance algorithms + * \class AwbImplementation + * \brief Pure virtual base class for AWB algorithms implementations + * + * The AwbImplementation class defines the interface for the AWB algorithm + * implementations. + * + * It is currently implemented by the AwbGrey and AwbBayes classes. + * + * The interface defines an init() function to initialize the algorithm with the + * content of the tuning file and two functions to compute colour gains + * according to the algorithm operating mode (auto or manual) in use. * - * This class is a base class for auto white balance algorithms. It defines an - * interface for the algorithms to implement, and is used by the IPAs to - * interact with the concrete implementation. + * The calculateAwb() function calculates colour gains given a set of statistics + * provided by the IPA module. It is used when the algorithm operates in auto + * mode and gains are dynamically computed given a new set of statistics from + * the AWB engine. + * + * The gainsFromColourTemperature() function instead computes the white balance + * gains from a colour temperature. This function does not take any statistics + * into account. It is used to compute the colour gains when the user manually + * specifies a colour temperature. */ /** - * \fn AwbAlgorithm::init() - * \brief Initialize the algorithm with the given tuning data - * \param[in] tuningData The tuning data to use for the algorithm + * \struct AwbImplementation::Result + * \brief The result of an AWB computation * - * \return 0 on success, a negative error code otherwise + * \var AwbImplementation::Result::gains + * \brief The computed white balance gains + * + * \var AwbImplementation::Result::colourTemperature + * \brief The computed colour temperature, in Kelvin + */ + +/** + * \fn AwbImplementation::~AwbImplementation + * \brief Virtual class destructor + */ + +/** + * \fn AwbImplementation::init() + * \param[in] tuningData + * \brief Initialize the algorithm by parsing \a tuningData */ /** - * \fn AwbAlgorithm::calculateAwb() + * \fn AwbImplementation::calculateAwb() * \brief Calculate AWB data from the given statistics * \param[in] stats The statistics to use for the calculation * \param[in] lux The lux value of the scene + * \param[in] range The colour temperature search limits * * Calculate an AwbResult object from the given statistics and lux value. A \a * lux value of 0 means it is unknown or invalid and the algorithm shall ignore @@ -106,7 +181,7 @@ namespace ipa { */ /** - * \fn AwbAlgorithm::gainsFromColourTemperature() + * \fn AwbImplementation::gainsFromColourTemperature() * \brief Compute white balance gains from a colour temperature * \param[in] colourTemperature The colour temperature in Kelvin * @@ -118,26 +193,225 @@ namespace ipa { */ /** - * \fn AwbAlgorithm::controls() - * \brief Get the controls info map for this algorithm + * \class AwbAlgorithmBase + * \brief Base class for AwbAlgorithm for non-templated functions implementation + * + * Base class for AwbAlgorithm where non-templated functions are implemented. + * IPA implementations shall use AwbAlgorithm 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 AWB 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 AwbAlgorithmBase::init(const ValueNode &tuningData) +{ + bayes_ = false; + + if (!tuningData.contains("algorithm")) + LOG(Awb, Info) << "No AWB algorithm specified, using grey world"; + + auto mode = tuningData["algorithm"].get("grey"); + if (mode == "grey") { + impl_ = std::make_unique(); + } else if (mode == "bayes") { + impl_ = std::make_unique(); + bayes_ = true; + } else { + LOG(Awb, Error) << "Unknown AWB algorithm: " << mode; + return -EINVAL; + } + + LOG(Awb, Debug) << "Using AWB algorithm: " << mode; + + int ret = impl_->init(tuningData); + if (ret) + return ret; + + controls_[&controls::ColourTemperature] = + ControlInfo(kMinColourTemperature, kMaxColourTemperature, + kDefaultColourTemperature); + controls_[&controls::AwbEnable] = ControlInfo(false, true); + + return parseModeConfigs(tuningData, controls::AwbAuto); +} + +/** + * \brief Configure the AWB algorithm + * \param[in] state The AWB active state + * \return 0 if successful, an error code otherwise + */ +int AwbAlgorithmBase::configure(awb::ActiveState &state) +{ + state.manual.gains = RGB{ 1.0 }; + auto gains = impl_->gainsFromColourTemperature(kDefaultColourTemperature); + if (gains) + state.automatic.gains = *gains; + else + state.automatic.gains = RGB{ 1.0 }; + + state.autoEnabled = true; + state.manual.colourTemperature = kDefaultColourTemperature; + state.automatic.colourTemperature = kDefaultColourTemperature; + + return 0; +} + +/** + * \brief Queue a Request to the AWB algorithm + * \param[in] state The AWB active state + * \param[in] frame The frame number + * \param[in] frameContext The AWB frame context + * \param[in] controls The list of controls part of the Request * - * \return The controls info map + * Queue a new Request to the AWB algorithm and modify its behaviour according + * to the provided controls. + * + * The currently handled controls are: + * - controls::AwbEnable + * - controls::AwbMode + * - controls::ColourGains + * - controls::ColourTemperature */ +void AwbAlgorithmBase::queueRequest(awb::ActiveState &state, + [[maybe_unused]] const uint32_t frame, + awb::FrameContext &frameContext, + const ControlList &controls) +{ + const auto &awbEnable = controls.get(controls::AwbEnable); + if (awbEnable && *awbEnable != state.autoEnabled) { + state.autoEnabled = *awbEnable; + + LOG(Awb, Debug) + << (*awbEnable ? "Enabling" : "Disabling") << " Awb"; + } + + auto mode = controls.get(controls::AwbMode); + if (mode) { + auto it = modes_.find(static_cast(*mode)); + if (it == modes_.end()) { + LOG(Awb, Error) << "Unsupported AWB mode " << *mode; + return; + } + + currentMode_ = &it->second; + } + + frameContext.autoEnabled = state.autoEnabled; + + if (frameContext.autoEnabled) + return; + + const auto &colourGains = controls.get(controls::ColourGains); + const auto &colourTemperature = controls.get(controls::ColourTemperature); + bool update = false; + if (colourGains) { + state.manual.gains.r() = (*colourGains)[0]; + state.manual.gains.b() = (*colourGains)[1]; + /* + * \todo Colour temperature reported in metadata is now + * incorrect, as we can't deduce the temperature from the gains. + * This will be fixed with the bayes AWB algorithm. + */ + update = true; + } else if (colourTemperature) { + state.manual.colourTemperature = *colourTemperature; + const auto &gains = impl_->gainsFromColourTemperature(*colourTemperature); + if (gains) { + state.manual.gains.r() = gains->r(); + state.manual.gains.b() = gains->b(); + update = true; + } + } + + if (update) + LOG(Awb, Debug) + << "Set colour gains to " << state.manual.gains; + + frameContext.gains = state.manual.gains; + frameContext.colourTemperature = state.manual.colourTemperature; +} /** - * \fn AwbAlgorithm::handleControls() - * \param[in] controls The controls to handle - * \brief Handle the controls supplied in a request + * \brief Set the gains and colour temperature values in \a frameContext + * \param[in] state The AWB active state + * \param[in] frameContext The AWB frame context + * + * If auto mode is enabled, take the most recently computed gains and use them + * for the current frame. Otherwise, if in manual mode, gains and colour + * temperature for a frame are set at queueRequest() time. */ +void AwbAlgorithmBase::prepare(awb::ActiveState &state, + awb::FrameContext &frameContext) +{ + if (frameContext.autoEnabled) { + frameContext.gains = state.automatic.gains; + frameContext.colourTemperature = state.automatic.colourTemperature; + } +} /** + * \brief Process AWB statistics to calculate gains and populate metadata + * \param[in] state The AWB active state + * \param[in] frameContext The AWB frame context + * \param[in] stats The AWB statistics + * \param[in] lux The lux value as estimated by the IPA module + * \param[out] metadata The metadata list + * + * Process \a stats to calculate new gains and colour temperature and populate + * \a metadata with the results. + */ +void AwbAlgorithmBase::process(awb::ActiveState &state, + awb::FrameContext &frameContext, + const AwbStats &stats, unsigned int lux, + ControlList &metadata) +{ + if (!stats.valid()) + return; + + auto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo, + currentMode_->ctHi }); + + /* + * Clamp the gain values to the hardware, according to the gainMin_ + * and gainMax_ values. + */ + awbResult.gains = awbResult.gains.clamp(gainMin_, gainMax_); + + /* Smooth color gains adjustments. */ + double speed = 0.2; + double ct = awbResult.colourTemperature; + ct = ct * speed + state.automatic.colourTemperature * (1 - speed); + + state.automatic.colourTemperature = awbResult.colourTemperature; + state.automatic.gains = awbResult.gains * speed + + state.automatic.gains * (1 - speed); + + /* Populate metadata. */ + metadata.set(controls::AwbEnable, frameContext.autoEnabled); + metadata.set(controls::ColourGains, { static_cast(frameContext.gains.r()), + static_cast(frameContext.gains.b()) }); + metadata.set(controls::ColourTemperature, frameContext.colourTemperature); + + LOG(Awb, Debug) << std::showpoint << "Means " << stats.rgbMeans() + << ", gains " << state.automatic.gains + << ", temp " << state.automatic.colourTemperature << "K"; +} + +/* * \brief Parse the mode configurations from the tuning data * \param[in] tuningData the ValueNode representing the tuning data * \param[in] def The default value for the AwbMode control * * Utility function to parse the tuning data for an AwbMode entry and read all - * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and - * populates AwbAlgorithm::modes_. For a list of possible modes see \ref + * provided modes. It adds controls::AwbMode to AwbAlgorithmBase::controls_ and + * populates AwbAlgorithmBase::modes_. For a list of possible modes see \ref * controls::AwbModeEnum. * * Each mode entry must contain a "lo" and "hi" key to specify the lower and @@ -156,15 +430,23 @@ namespace ipa { * ... * \endcode * - * If \a def is supplied but not contained in the the \a tuningData, -EINVAL is + * If \a def is supplied but not contained in the \a tuningData, -EINVAL is * returned. * + * AwbModes are only used by the AwbBayes implementation. + * * \sa controls::AwbModeEnum * \return Zero on success, negative error code otherwise */ -int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData, - const ControlValue &def) +int AwbAlgorithmBase::parseModeConfigs(const ValueNode &tuningData, + const ControlValue &def) { + if (!bayes_) { + /* AwbGrey does not support and does not use modes. */ + currentMode_ = &AwbGreyMode; + return 0; + } + std::vector availableModes; const ValueNode &modes = tuningData[controls::AwbMode.name()]; @@ -227,37 +509,83 @@ int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData, } controls_[&controls::AwbMode] = ControlInfo(availableModes, def); + currentMode_ = &modes_[controls::AwbAuto]; return 0; } /** - * \class AwbAlgorithm::ModeConfig + * \var AwbAlgorithmBase::controls_ + * \brief Controls info map for the controls registered by the AWB algorithm + */ + +/** + * \var AwbAlgorithmBase::gainMin_ + * \brief The minimum supported gain value + * + * Minimum gain value used to clamp the AWB algorithm calculation results in the + * range supported by the platform AWB engine. + * + * The min and max gain values are initialized by AwbAlgorithm::init(). + */ + +/** + * \var AwbAlgorithmBase::gainMax_ + * \brief The maximum supported gain value + * + * Maximum gain value used to clamp the AWB algorithm calculation results in the + * range supported by the platform AWB engine. + * + * The min and max gain values are initialized by AwbAlgorithm::init(). + */ + +/* + * \class AwbAlgorithmBase::ModeConfig * \brief Holds the configuration of a single AWB mode * * AWB modes limit the regulation of the AWB algorithm to a specific range of - * colour temperatures. + * colour temperatures. Use by AwbBayes only. */ -/** - * \var AwbAlgorithm::ModeConfig::ctLo +/* + * \var AwbAlgorithmBase::ModeConfig::ctLo * \brief The lowest valid colour temperature of that mode */ -/** - * \var AwbAlgorithm::ModeConfig::ctHi +/* + * \var AwbAlgorithmBase::ModeConfig::ctHi * \brief The highest valid colour temperature of that mode */ +/* + * \var AwbAlgorithmBase::modes_ + * \brief Map of all configured modes + * \sa AwbAlgorithmBase::parseModeConfigs + */ + /** - * \var AwbAlgorithm::controls_ - * \brief Controls info map for the controls provided by the algorithm + * \class AwbAlgorithm + * \brief The libipa AWB algorithm + * \tparam Q The fixedpoint register representation of gain values + * + * Implement the AWB algorithm for libipa. + * + * The AwbAlgorithm class implements an interface similar in spirit to the one + * of the Algorithm class. IPA modules are expected to store an instance of + * AwbAlgorithm as class member, template it with the AWB engine gain register + * representation and call its function in their implementations of the + * Algorithm interface. + * + * The AwbAlgorithm instantiates an AwbImplementation implementation (AwbGrey or + * AwbBayes) at init() time by parsing the tuning data and uses it to compute + * the RGB gains and estimate a colour temperature given a set of statistics + * in the form of a AwbStats derived implementation. */ /** - * \var AwbAlgorithm::modes_ - * \brief Map of all configured modes - * \sa AwbAlgorithm::parseModeConfigs + * \fn AwbAlgorithm::init() + * \param[in] controls The info map of the IPA controls + * \copydoc AwbAlgorithmBase::init() */ } /* namespace ipa */ diff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h index 09c00e47d604..d35a85167c20 100644 --- a/src/ipa/libipa/awb.h +++ b/src/ipa/libipa/awb.h @@ -2,11 +2,12 @@ /* * Copyright (C) 2024 Ideas on Board Oy * - * Generic AWB algorithms + * libIPA AWB algorithms */ #pragma once +#include #include #include @@ -20,46 +21,113 @@ namespace libcamera { namespace ipa { -struct AwbResult { +namespace awb { + +struct Context { RGB gains; - double colourTemperature; + unsigned int colourTemperature; }; +struct ActiveState { + Context manual; + Context automatic; + + bool autoEnabled; +}; + +struct FrameContext : public Context { + bool autoEnabled; +}; + +} /* namespace awb */ + struct AwbStats { virtual double computeColourError(const RGB &gains) const = 0; + virtual bool valid() const = 0; virtual RGB rgbMeans() const = 0; protected: ~AwbStats() = default; }; -class AwbAlgorithm +class AwbImplementation { public: - virtual ~AwbAlgorithm() = default; + struct Result { + RGB gains; + unsigned int colourTemperature; + }; + virtual ~AwbImplementation() = default; virtual int init(const ValueNode &tuningData) = 0; - virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0; - virtual std::optional> gainsFromColourTemperature(double colourTemperature) = 0; + virtual Result calculateAwb(const AwbStats &stats, unsigned int lux, + std::array range) = 0; + virtual std::optional> + gainsFromColourTemperature(double colourTemperature) = 0; +}; - const ControlInfoMap::Map &controls() const - { - return controls_; - } +class AwbAlgorithmBase +{ +public: + int init(const ValueNode &tuningData); + + int configure(awb::ActiveState &state); - virtual void handleControls([[maybe_unused]] const ControlList &controls) {} + void queueRequest(awb::ActiveState &state, + const uint32_t frame, + awb::FrameContext &frameContext, + const ControlList &controls); + + void prepare(awb::ActiveState &state, awb::FrameContext &frameContext); + + void process(awb::ActiveState &state, awb::FrameContext &frameContext, + const AwbStats &stats, unsigned int lux, + ControlList &metadata); protected: - int parseModeConfigs(const ValueNode &tuningData, - const ControlValue &def = {}); + AwbAlgorithmBase() = default; + ControlInfoMap::Map controls_; + float gainMin_; + float gainMax_; + +private: struct ModeConfig { double ctHi; double ctLo; }; - ControlInfoMap::Map controls_; - std::map modes_; + /* AwbGrey does not support modes; */ + static constexpr ModeConfig AwbGreyMode = { 0.0, 0.0 }; + + int parseModeConfigs(const ValueNode &tuningData, + const ControlValue &def = {}); + + std::map modes_; + const ModeConfig *currentMode_ = nullptr; + std::unique_ptr impl_; + bool bayes_ = false; +}; + +template +class AwbAlgorithm : public AwbAlgorithmBase +{ +public: + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) + { + AwbAlgorithmBase::init(tuningData); + + gainMin_ = std::max(Q::TraitsType::min, 1.0f); + gainMax_ = Q::TraitsType::max; + + controls_[&controls::ColourGains] = + ControlInfo(gainMin_, gainMax_, + Span{ { 1.0f, 1.0f } }); + + controls.insert(controls_.begin(), controls_.end()); + + return 0; + } }; } /* namespace ipa */ diff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp index 9fd85e5a4505..2c10f9c4c82d 100644 --- a/src/ipa/libipa/awb_bayes.cpp +++ b/src/ipa/libipa/awb_bayes.cpp @@ -140,11 +140,6 @@ void Interpolator::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl * \brief How far to wander off CT curve towards "more green" */ -/** - * \var AwbBayes::currentMode_ - * \brief The currently selected mode - */ - int AwbBayes::init(const ValueNode &tuningData) { int ret = colourGainCurve_.readYaml(tuningData["colourGains"], "ct", "gains"); @@ -172,14 +167,6 @@ int AwbBayes::init(const ValueNode &tuningData) return ret; } - ret = parseModeConfigs(tuningData, controls::AwbAuto); - if (ret) { - LOG(Awb, Error) - << "Failed to parse mode parameter from tuning file"; - return ret; - } - currentMode_ = &modes_[controls::AwbAuto]; - transversePos_ = tuningData["transversePos"].get(0.01); transverseNeg_ = tuningData["transverseNeg"].get(0.01); if (transversePos_ <= 0 || transverseNeg_ <= 0) { @@ -260,18 +247,16 @@ int AwbBayes::readPriors(const ValueNode &tuningData) return 0; } -void AwbBayes::handleControls(const ControlList &controls) -{ - auto mode = controls.get(controls::AwbMode); - if (mode) { - auto it = modes_.find(static_cast(*mode)); - if (it != modes_.end()) - currentMode_ = &it->second; - else - LOG(Awb, Error) << "Unsupported AWB mode " << *mode; - } -} - +/** + * \brief Compute white balance gains from a colour temperature + * \param[in] colourTemperature The colour temperature in Kelvin + * + * Compute the white balance gains from a \a colourTemperature. This function + * does not take any statistics into account. It simply interpolates the colour + * gains curve on a new colour temperature. + * + * \return The RGB gain values adjusted to \a temperatureK + */ std::optional> AwbBayes::gainsFromColourTemperature(double colourTemperature) { /* @@ -283,7 +268,9 @@ std::optional> AwbBayes::gainsFromColourTemperature(double colourTem return RGB{ { gains[0], 1.0, gains[1] } }; } -AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux) +AwbImplementation::Result +AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux, + std::array range) { ipa::Pwl prior; if (lux > 0) { @@ -295,7 +282,7 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux) prior.append(0, 1.0); } - double t = coarseSearch(prior, stats); + double t = coarseSearch(prior, stats, range); double r = ctR_.eval(t); double b = ctB_.eval(t); LOG(Awb, Debug) @@ -316,14 +303,15 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux) << "After fine search: r " << r << " b " << b << " (gains r " << 1 / r << " b " << 1 / b << ")"; - return { { { 1.0 / r, 1.0, 1.0 / b } }, t }; + return { { { 1.0 / r, 1.0, 1.0 / b } }, static_cast(t) }; } -double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const +double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats, + Span range) const { std::vector points; size_t bestPoint = 0; - double t = currentMode_->ctLo; + double t = range[0]; int spanR = -1; int spanB = -1; LimitsRecorder errorLimits; @@ -345,14 +333,14 @@ double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) cons if (points.back().y() < points[bestPoint].y()) bestPoint = points.size() - 1; - if (t == currentMode_->ctHi) + if (t == range[1]) break; /* * Ensure even steps along the r/b curve by scaling them by the * current t. */ - t = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi); + t = std::min(t + t / 10 * kSearchStep, range[1]); } t = points[bestPoint].x(); diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h index 1e3373676bc0..8d4cff076d4a 100644 --- a/src/ipa/libipa/awb_bayes.h +++ b/src/ipa/libipa/awb_bayes.h @@ -20,22 +20,24 @@ namespace libcamera { namespace ipa { -class AwbBayes : public AwbAlgorithm +class AwbBayes : public AwbImplementation { public: AwbBayes() = default; int init(const ValueNode &tuningData) override; - AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override; + AwbImplementation::Result + calculateAwb(const AwbStats &stats, unsigned int lux, + std::array range) override; std::optional> gainsFromColourTemperature(double temperatureK) override; - void handleControls(const ControlList &controls) override; private: int readPriors(const ValueNode &tuningData); void fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior, const AwbStats &stats) const; - double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const; + double coarseSearch(const ipa::Pwl &prior, const AwbStats &stats, + Span range) const; double interpolateQuadratic(ipa::Pwl::Point const &a, ipa::Pwl::Point const &b, ipa::Pwl::Point const &c) const; @@ -50,8 +52,6 @@ private: double transversePos_; double transverseNeg_; - - ModeConfig *currentMode_ = nullptr; }; } /* namespace ipa */ diff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp index b14b096810ae..b12229023883 100644 --- a/src/ipa/libipa/awb_grey.cpp +++ b/src/ipa/libipa/awb_grey.cpp @@ -60,6 +60,7 @@ int AwbGrey::init(const ValueNode &tuningData) * \brief Calculate AWB data from the given statistics * \param[in] stats The statistics to use for the calculation * \param[in] lux The lux value of the scene + * \param[in] range The colour temperature search limits * * The colour temperature is estimated based on the colours::estimateCCT() * function. The gains are calculated purely based on the RGB means provided by @@ -70,9 +71,11 @@ int AwbGrey::init(const ValueNode &tuningData) * * \return The AWB result */ -AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux) +AwbImplementation::Result +AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux, + [[maybe_unused]] std::array range) { - AwbResult result; + AwbImplementation::Result result; auto means = stats.rgbMeans(); result.colourTemperature = estimateCCT(means); @@ -93,15 +96,16 @@ AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned * * Compute the white balance gains from a \a colourTemperature. This function * does not take any statistics into account. It simply interpolates the colour - * gains configured in the colour temperature curve. + * gains curve (if provided in the tuning file) on a new colour temperature. * - * \return The colour gains if a colour temperature curve is available, - * [1, 1, 1] otherwise. + * \return The RGB gain values adjusted to \a colourTemperature or std::nullopt if + * no gain curve is specified in the tuning data */ std::optional> AwbGrey::gainsFromColourTemperature(double colourTemperature) { if (!colourGainCurve_) { - LOG(Awb, Error) << "No gains defined"; + LOG(Awb, Info) << "No gains curve defined, " + << "unable to interpolate gains to colour temperature"; return std::nullopt; } diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h index 154a2af97f15..d1b0088f0470 100644 --- a/src/ipa/libipa/awb_grey.h +++ b/src/ipa/libipa/awb_grey.h @@ -18,13 +18,15 @@ namespace libcamera { namespace ipa { -class AwbGrey : public AwbAlgorithm +class AwbGrey : public AwbImplementation { public: AwbGrey() = default; int init(const ValueNode &tuningData) override; - AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override; + AwbImplementation::Result + calculateAwb(const AwbStats &stats, unsigned int lux, + std::array range) override; std::optional> gainsFromColourTemperature(double colourTemperature) override; private: diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp index 0eb05c2b6e1a..cf8dc14308e2 100644 --- a/src/ipa/rkisp1/algorithms/awb.cpp +++ b/src/ipa/rkisp1/algorithms/awb.cpp @@ -8,17 +8,12 @@ #include "awb.h" #include -#include #include -#include - #include -#include "libipa/awb_bayes.h" -#include "libipa/awb_grey.h" -#include "libipa/colours.h" +#include "libcamera/internal/vector.h" /** * \file awb.h @@ -28,20 +23,8 @@ namespace libcamera { namespace ipa::rkisp1::algorithms { -/** - * \class Awb - * \brief Manage the white balance with automatic and manual controls - */ - LOG_DEFINE_CATEGORY(RkISP1Awb) -constexpr int32_t kMinColourTemperature = 2500; -constexpr int32_t kMaxColourTemperature = 10000; -constexpr int32_t kDefaultColourTemperature = 5000; - -/* Minimum mean value below which AWB can't operate. */ -constexpr double kMeanMinThreshold = 2.0; - class RkISP1AwbStats final : public AwbStats { public: @@ -70,12 +53,26 @@ public: return rgbMeans_; } + bool valid() const override + { + /* Minimum mean value below which AWB can't operate. */ + constexpr double minValue = 2.0; + + return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue || + rgbMeans_.b() > minValue; + } + private: RGB rgbMeans_; double rg_; double bg_; }; +/** + * \class Awb + * \brief Manage the white balance with automatic and manual controls + */ + Awb::Awb() : rgbMode_(false) { @@ -86,40 +83,7 @@ Awb::Awb() */ int Awb::init(IPAContext &context, const ValueNode &tuningData) { - auto &cmap = context.ctrlMap; - cmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature, - kMaxColourTemperature, - kDefaultColourTemperature); - cmap[&controls::AwbEnable] = ControlInfo(false, true); - - cmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f, - Span{ { 1.0f, 1.0f } }); - - if (!tuningData.contains("algorithm")) - LOG(RkISP1Awb, Info) << "No AWB algorithm specified." - << " Default to grey world"; - - auto mode = tuningData["algorithm"].get("grey"); - if (mode == "grey") { - awbAlgo_ = std::make_unique(); - } else if (mode == "bayes") { - awbAlgo_ = std::make_unique(); - } else { - LOG(RkISP1Awb, Error) << "Unknown AWB algorithm: " << mode; - return -EINVAL; - } - LOG(RkISP1Awb, Debug) << "Using AWB algorithm: " << mode; - - int ret = awbAlgo_->init(tuningData); - if (ret) { - LOG(RkISP1Awb, Error) << "Failed to init AWB algorithm"; - return ret; - } - - const auto &src = awbAlgo_->controls(); - cmap.insert(src.begin(), src.end()); - - return 0; + return awbAlgo_.init(tuningData, context.ctrlMap); } /** @@ -128,16 +92,7 @@ int Awb::init(IPAContext &context, const ValueNode &tuningData) int Awb::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { - context.activeState.awb.manual.gains = RGB{ 1.0 }; - auto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature); - if (gains) - context.activeState.awb.automatic.gains = *gains; - else - context.activeState.awb.automatic.gains = RGB{ 1.0 }; - - context.activeState.awb.autoEnabled = true; - context.activeState.awb.manual.temperatureK = kDefaultColourTemperature; - context.activeState.awb.automatic.temperatureK = kDefaultColourTemperature; + awbAlgo_.configure(context.activeState.awb); /* * Define the measurement window for AWB as a centered rectangle @@ -156,56 +111,12 @@ int Awb::configure(IPAContext &context, /** * \copydoc libcamera::ipa::Algorithm::queueRequest */ -void Awb::queueRequest(IPAContext &context, - [[maybe_unused]] const uint32_t frame, +void Awb::queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) { - auto &awb = context.activeState.awb; - - const auto &awbEnable = controls.get(controls::AwbEnable); - if (awbEnable && *awbEnable != awb.autoEnabled) { - awb.autoEnabled = *awbEnable; - - LOG(RkISP1Awb, Debug) - << (*awbEnable ? "Enabling" : "Disabling") << " AWB"; - } - - awbAlgo_->handleControls(controls); - - frameContext.awb.autoEnabled = awb.autoEnabled; - - if (awb.autoEnabled) - return; - - const auto &colourGains = controls.get(controls::ColourGains); - const auto &colourTemperature = controls.get(controls::ColourTemperature); - bool update = false; - if (colourGains) { - awb.manual.gains.r() = (*colourGains)[0]; - awb.manual.gains.b() = (*colourGains)[1]; - /* - * \todo Colour temperature reported in metadata is now - * incorrect, as we can't deduce the temperature from the gains. - * This will be fixed with the bayes AWB algorithm. - */ - update = true; - } else if (colourTemperature) { - awb.manual.temperatureK = *colourTemperature; - const auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature); - if (gains) { - awb.manual.gains.r() = gains->r(); - awb.manual.gains.b() = gains->b(); - update = true; - } - } - - if (update) - LOG(RkISP1Awb, Debug) - << "Set colour gains to " << awb.manual.gains; - - frameContext.awb.gains = awb.manual.gains; - frameContext.awb.temperatureK = awb.manual.temperatureK; + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); } /** @@ -214,15 +125,7 @@ void Awb::queueRequest(IPAContext &context, void Awb::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - /* - * This is the latest time we can read the active state. This is the - * most up-to-date automatic values we can read. - */ - if (frameContext.awb.autoEnabled) { - const auto &awb = context.activeState.awb; - frameContext.awb.gains = awb.automatic.gains; - frameContext.awb.temperatureK = awb.automatic.temperatureK; - } + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); auto gainConfig = params->block(); gainConfig.setEnabled(true); @@ -291,15 +194,6 @@ void Awb::process(IPAContext &context, const rkisp1_stat_buffer *stats, ControlList &metadata) { - IPAActiveState &activeState = context.activeState; - - metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled); - metadata.set(controls::ColourGains, { - static_cast(frameContext.awb.gains.r()), - static_cast(frameContext.awb.gains.b()) - }); - metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK); - if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) { LOG(RkISP1Awb, Error) << "AWB data is missing in statistics"; return; @@ -313,45 +207,14 @@ void Awb::process(IPAContext &context, return; } - RGB rgbMeans = calculateRgbMeans(frameContext, awb); - - /* - * If the means are too small we don't have enough information to - * meaningfully calculate gains. Freeze the algorithm in that case. - */ - if (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold && - rgbMeans.b() < kMeanMinThreshold) - return; + RkISP1AwbStats awbStats = calculateRgbMeans(frameContext, awb); - RkISP1AwbStats awbStats{ rgbMeans }; - AwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux); - - /* - * Clamp the gain values to the hardware, which expresses gains as Q2.8 - * unsigned integer values. Set the minimum just above zero to avoid - * divisions by zero when computing the raw means in subsequent - * iterations. - */ - awbResult.gains = awbResult.gains.clamp(1.0 / 256, 1023.0 / 256); - - /* Filter the values to avoid oscillations. */ - double speed = 0.2; - double ct = awbResult.colourTemperature; - ct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed); - awbResult.gains = awbResult.gains * speed + - activeState.awb.automatic.gains * (1 - speed); - - activeState.awb.automatic.temperatureK = static_cast(ct); - activeState.awb.automatic.gains = awbResult.gains; - - LOG(RkISP1Awb, Debug) - << std::showpoint - << "Means " << rgbMeans << ", gains " - << activeState.awb.automatic.gains << ", temp " - << activeState.awb.automatic.temperatureK << "K"; + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + frameContext.lux.lux, metadata); } -RGB Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const +RkISP1AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, + const rkisp1_cif_isp_awb_stat *awb) const { Vector rgbMeans; @@ -419,7 +282,7 @@ RGB Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rk */ rgbMeans /= frameContext.awb.gains.max(0.01); - return rgbMeans; + return RkISP1AwbStats(rgbMeans); } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h index 60d9ef111495..89a3e37b20f9 100644 --- a/src/ipa/rkisp1/algorithms/awb.h +++ b/src/ipa/rkisp1/algorithms/awb.h @@ -7,17 +7,25 @@ #pragma once -#include "libcamera/internal/vector.h" +#include + +#include + +#include "libcamera/internal/value_node.h" #include "libipa/awb.h" -#include "libipa/interpolator.h" +#include "libipa/fixedpoint.h" #include "algorithm.h" +#include "ipa_context.h" +#include "params.h" namespace libcamera { namespace ipa::rkisp1::algorithms { +class RkISP1AwbStats; + class Awb : public Algorithm { public: @@ -38,10 +46,10 @@ public: ControlList &metadata) override; private: - RGB calculateRgbMeans(const IPAFrameContext &frameContext, - const rkisp1_cif_isp_awb_stat *awb) const; + RkISP1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext, + const rkisp1_cif_isp_awb_stat *awb) const; - std::unique_ptr awbAlgo_; + AwbAlgorithm> awbAlgo_; bool rgbMode_; }; diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp index 6bb9c7bcfcb3..633dc2ddb834 100644 --- a/src/ipa/rkisp1/algorithms/ccm.cpp +++ b/src/ipa/rkisp1/algorithms/ccm.cpp @@ -77,7 +77,7 @@ int Ccm::configure(IPAContext &context, { auto &as = context.activeState; as.ccm.manual = kIdentity3x3; - as.ccm.automatic = ccm_.getInterpolated(as.awb.automatic.temperatureK); + as.ccm.automatic = ccm_.getInterpolated(as.awb.automatic.colourTemperature); return 0; } @@ -140,7 +140,7 @@ void Ccm::prepare(IPAContext &context, const uint32_t frame, return; } - uint32_t ct = frameContext.awb.temperatureK; + uint32_t ct = frameContext.awb.colourTemperature; if (frame > 0 && ct == ct_) { frameContext.ccm.ccm = context.activeState.ccm.automatic; return; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index faf88effde01..06f5913c5750 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -512,7 +512,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, IPAFrameContext &frameContext, RkISP1Params *params) { - uint32_t ct = frameContext.awb.temperatureK; + uint32_t ct = frameContext.awb.colourTemperature; unsigned int quantizedCt = quantize(ct, kColourTemperatureQuantization); /* Check if we can skip the update. */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index e5f70d934d67..c36c1f7e0084 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -25,6 +25,7 @@ #include "libcamera/internal/vector.h" #include "libipa/agc_mean_luminance.h" +#include "libipa/awb.h" #include "libipa/camera_sensor_helper.h" #include "libipa/fc_queue.h" #include "libipa/fixedpoint.h" @@ -48,15 +49,17 @@ struct IPAHwSettings { bool compand; }; +struct RKISP1AwbSession { + struct rkisp1_cif_isp_window measureWindow; + bool enabled; +}; + struct IPASessionConfiguration { struct { struct rkisp1_cif_isp_window measureWindow; } agc; - struct { - struct rkisp1_cif_isp_window measureWindow; - bool enabled; - } awb; + struct RKISP1AwbSession awb; struct { bool supported; @@ -100,17 +103,7 @@ struct IPAActiveState { utils::Duration maxFrameDuration; } agc; - struct { - struct AwbState { - RGB gains; - unsigned int temperatureK; - }; - - AwbState manual; - AwbState automatic; - - bool autoEnabled; - } awb; + ipa::awb::ActiveState awb; struct { Matrix manual; @@ -176,11 +169,7 @@ struct IPAFrameContext : public FrameContext { bool autoGainModeChange; } agc; - struct { - RGB gains; - bool autoEnabled; - unsigned int temperatureK; - } awb; + ipa::awb::FrameContext awb; struct { float actualBrightness; From patchwork Wed Aug 5 16:12:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27625 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 D3878C3334 for ; Wed, 5 Aug 2026 16:13:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3634C6812E; Wed, 5 Aug 2026 18:13:30 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="v7Xbz5f+"; 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 EE7A668117 for ; Wed, 5 Aug 2026 18:13:25 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 050A0981; Wed, 5 Aug 2026 18:12:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946335; bh=dhl5gblzCqtHc/mQBW5GOxetdk2axICofVb774mO1ss=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=v7Xbz5f+4h7uPRyTO+jgZi7TLcs4c8s6sL7J7KAHeH/lHJK1a9/+JBCJ1rUqkBJk/ eu4Qay2Nx+oU8HfcMHRzSpr8PN/eaGMXF440zSQef1m12E6uFtk/G3HN0Gu++jOzjO QvOfovUIrUTnKBdMRSitL16LVuzl1SzKuqO6eiHA= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:44 +0200 Subject: [PATCH v7 02/32] ipa: mali-c55: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-2-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=13303; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=dhl5gblzCqtHc/mQBW5GOxetdk2axICofVb774mO1ss=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EZlmqoAzIllq4lsgcHK/kTFh/EGt2nF/Gl7 pJf+MepiLuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhGQAKCRByNAaPFqFW PLYfD/4h9bsrCnRfSYor2ARl8eXX0s+QovOw1QrGqTLxbNYTm4NqxccfG72ALCDMdY6veKQpx/7 6m8B25CPmOAAtISB5OF3Dfqz3EK/KGQ58Gqa2zU3XowVBLg/xAEbmsOLqDYSok2epglsvgEoixH uSoXnn7vFySl1TUBGQ0dDxGU5zRNB4Crff2tVU3wwQUgQE1qNQzywrJJKwDxU/jZYH/MYLX+cUt en1zDikeCaepsAzLf1z2aBGZIGNnFwX+YM4TSoM1T1xGliWF2viCyuLbzYnFBeOOwyWxbAPZFaZ Dj11ErlyAUaoD7rlORlaYVWNItEGrxKBmRiJ7KA+0WxRd1aPoNxY2JASePliMhAMAHLdZpJe+pd gQmpuxuW/OQNIyuINm8FMCaPK9iYg4DuCupHYdWhqxpH9SBrhQlObY1jndL3CoziLCGtgnJ/OJV PQ+EKV/GM0Ma1oo52/niyD5LJCgj5J73zBWnFSZ1v4+UHXSnSYERDic0cPzAKLvsO8oNpDYZGkW sRs9+JxH5RmaFWF6HU64TLmhWnlErwFxrNfEIlAVDm9ug9WwGr93Nwhd1oRTSiAeweGIDXHJXnZ RSB1c3bKXfX9eLLL6tMraWKVSiS64qMnbzW08WY6hCiCSxl7GLUkNAO5CHgYJa2MVwUYcHP/yri e1I/zBKcsVgUKbA== 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" Port the Mali-C55 Awb algorithm to use the new libipa implementation of AwbAlgorithm. The awbAlgo_ class member is initialized with the Q<4, 8> type that represents the register format and the MaliC55AwbStats type handles the WB statistics format as produced by the Mali-C55 which already calculates the mean values as R/G and B/G values. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally Tested-by: Daniel Scally --- src/ipa/mali-c55/algorithms/awb.cpp | 223 +++++++++++++++++++++--------------- src/ipa/mali-c55/algorithms/awb.h | 28 ++++- src/ipa/mali-c55/ipa_context.cpp | 3 + src/ipa/mali-c55/ipa_context.h | 11 +- 4 files changed, 157 insertions(+), 108 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp index 8a671b52be59..353a9f0d3f97 100644 --- a/src/ipa/mali-c55/algorithms/awb.cpp +++ b/src/ipa/mali-c55/algorithms/awb.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2024, Ideas On Board Oy * - * Mali C55 grey world auto white balance algorithm + * Mali C55 auto white balance algorithm */ #include "awb.h" @@ -14,60 +14,91 @@ #include -#include "libipa/fixedpoint.h" - namespace libcamera { namespace ipa::mali_c55::algorithms { LOG_DEFINE_CATEGORY(MaliC55Awb) -/* Number of frames at which we should run AWB at full speed */ -static constexpr uint32_t kNumStartupFrames = 4; +/* \todo Mali-C55 doesn't support the Lux algorithm. */ +static constexpr unsigned int kDefaultLux = 500; -Awb::Awb() +class MaliC55AwbStats final : public AwbStats { +public: + MaliC55AwbStats() = default; + MaliC55AwbStats(const RGB &rgbMeans) + { + /* The Mali-C55 ISP already provides stats as R/G and B/G ratios. */ + + rgbMeans_[0] = rgbMeans.r() * rgbMeans.g(); + rgbMeans_[1] = 1.0; + rgbMeans_[2] = rgbMeans.b() * rgbMeans.g(); + + rg_ = rgbMeans_.r(); + bg_ = rgbMeans_.b(); + } + + double computeColourError(const RGB &gains) const override + { + /* + * Compute the sum of the squared colour error (non-greyness) as + * it appears in the log likelihood equation. + */ + double deltaR = gains.r() * rg_ - 1.0; + double deltaB = gains.b() * bg_ - 1.0; + double delta2 = deltaR * deltaR + deltaB * deltaB; + + return delta2; + } + + RGB rgbMeans() const override + { + return rgbMeans_; + } + + bool valid() const override + { + /* Minimum mean value below which AWB can't operate. */ + constexpr double minValue = 0.2; + + return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue || + rgbMeans_.b() > minValue; + } + +private: + RGB rgbMeans_; + double rg_; + double bg_; +}; + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Awb::init(IPAContext &context, const ValueNode &tuningData) +{ + return awbAlgo_.init(tuningData, context.ctrlMap); } -int Awb::configure([[maybe_unused]] IPAContext &context, +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Awb::configure(IPAContext &context, [[maybe_unused]] const IPACameraSensorInfo &configInfo) { - /* - * Initially we have no idea what the colour balance will be like, so - * for the first frame we will make no assumptions and leave the R/B - * channels unmodified. - */ - context.activeState.awb.rGain = 1.0f; - context.activeState.awb.bGain = 1.0f; - - return 0; + return awbAlgo_.configure(context.activeState.awb); } -void Awb::fillGainsParamBlock(MaliC55Params *params, IPAContext &context, - IPAFrameContext &frameContext) +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) { - UQ<4, 8> rGain = context.activeState.awb.rGain; - UQ<4, 8> bGain = context.activeState.awb.bGain; - - /* - * The gains here map as follows: - * gain00 = R - * gain01 = Gr - * gain10 = Gb - * gain11 = B - * - * This holds true regardless of the bayer order of the input data, as - * the mapping is done internally in the ISP. - */ - auto block = params->block(); - - block->gain00 = rGain.quantized(); - block->gain01 = UQ<4, 8>(1.0f).quantized(); - block->gain10 = UQ<4, 8>(1.0f).quantized(); - block->gain11 = bGain.quantized(); - - frameContext.awb.rGain = rGain; - frameContext.awb.bGain = bGain; + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); } void Awb::fillConfigParamBlock(MaliC55Params *params) @@ -117,10 +148,33 @@ void Awb::fillConfigParamBlock(MaliC55Params *params) block->cb_low = 64; } +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ void Awb::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, MaliC55Params *params) { - fillGainsParamBlock(params, context, frameContext); + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); + + /* + * The gains here map as follows: + * gain00 = R + * gain01 = Gr + * gain10 = Gb + * gain11 = B + * + * This holds true regardless of the bayer order of the input data, as + * the mapping is done internally in the ISP. + */ + auto block = params->block(); + block.setEnabled(true); + + block->gain00 = UQ<4, 8>(static_cast(frameContext.awb.gains.r())) + .quantized(); + block->gain01 = UQ<4, 8>(1.0f).quantized(); + block->gain10 = UQ<4, 8>(1.0f).quantized(); + block->gain11 = UQ<4, 8>(static_cast(frameContext.awb.gains.b())) + .quantized(); if (frame > 0) return; @@ -128,45 +182,43 @@ void Awb::prepare(IPAContext &context, const uint32_t frame, fillConfigParamBlock(params); } -void Awb::process(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, - [[maybe_unused]] ControlList &metadata) +MaliC55AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats) const { - const struct mali_c55_awb_average_ratios *awb_ratios = stats->awb_ratios; + const struct mali_c55_awb_average_ratios *awb = stats->awb_ratios; /* * The ISP produces average R:G and B:G ratios for zones. We take the - * average of all the zones with data and simply invert them to provide - * gain figures that we can apply to approximate a grey world. + * average of all the zones with data and calculate the mean values. */ - unsigned int counted_zones = 0; - float rgSum = 0, bgSum = 0; + unsigned int active_zones = 0; + double rgSum = 0, bgSum = 0; for (unsigned int i = 0; i < 225; i++) { - if (!awb_ratios[i].num_pixels) + if (!awb[i].num_pixels) continue; /* - * The statistics are in Q4.8 format, so we convert to float + * The statistics are in Q4.8 format, so we convert to double * here. */ - rgSum += UQ<4, 8>(awb_ratios[i].avg_rg_gr).value(); - bgSum += UQ<4, 8>(awb_ratios[i].avg_bg_br).value(); - counted_zones++; + rgSum += UQ<4, 8>(awb[i].avg_rg_gr).value(); + bgSum += UQ<4, 8>(awb[i].avg_bg_br).value(); + active_zones++; } /* * Sometimes the first frame's statistics have no valid pixels, in which * case we'll just assume a grey world until they say otherwise. */ - float rgAvg, bgAvg; - if (!counted_zones) { - rgAvg = 1.0; - bgAvg = 1.0; - } else { - rgAvg = rgSum / counted_zones; - bgAvg = bgSum / counted_zones; - } + if (!active_zones) + return {}; + + RGB rgbMeans = { { + rgSum / active_zones, + 1.0, + bgSum / active_zones, + } }; /* * The statistics are generated _after_ white balancing is performed in @@ -174,41 +226,22 @@ void Awb::process(IPAContext &context, const uint32_t frame, * figure by the gains that were applied when the statistics for this * frame were generated. */ - float rRatio = rgAvg / frameContext.awb.rGain.value(); - float bRatio = bgAvg / frameContext.awb.bGain.value(); + rgbMeans /= frameContext.awb.gains.max(0.01); - /* - * And then we can simply invert the ratio to find the gain we should - * apply. - */ - float rGain = 1 / rRatio; - float bGain = 1 / bRatio; + return MaliC55AwbStats(rgbMeans); +} - /* - * Running at full speed, this algorithm results in oscillations in the - * colour balance. To remove those we dampen the speed at which it makes - * changes in gain, unless we're in the startup phase in which case we - * want to fix the miscolouring as quickly as possible. - */ - float speed = frame < kNumStartupFrames ? 1.0f : 0.2f; - rGain = speed * rGain + context.activeState.awb.rGain.value() * (1.0f - speed); - bGain = speed * bGain + context.activeState.awb.bGain.value() * (1.0f - speed); - - context.activeState.awb.rGain = rGain; - context.activeState.awb.bGain = bGain; - - metadata.set(controls::ColourGains, { - frameContext.awb.rGain.value(), - frameContext.awb.bGain.value(), - }); - - LOG(MaliC55Awb, Debug) << "For frame number " << frame << ": " - << "Average R/G Ratio: " << rgAvg - << ", Average B/G Ratio: " << bgAvg - << "\nrGain applied to this frame: " << frameContext.awb.rGain - << ", bGain applied to this frame: " << frameContext.awb.bGain - << "\nrGain to apply: " << context.activeState.awb.rGain - << ", bGain to apply: " << context.activeState.awb.bGain; +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const mali_c55_stats_buffer *stats, + ControlList &metadata) +{ + MaliC55AwbStats awbStats = calculateRgbMeans(frameContext, stats); + + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + kDefaultLux, metadata); } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/mali-c55/algorithms/awb.h b/src/ipa/mali-c55/algorithms/awb.h index 683a62af263a..4be7a5917ac5 100644 --- a/src/ipa/mali-c55/algorithms/awb.h +++ b/src/ipa/mali-c55/algorithms/awb.h @@ -2,24 +2,41 @@ /* * Copyright (C) 2024, Ideas on Board Oy * - * Mali C55 grey world auto white balance algorithm + * Mali C55 auto white balance algorithm */ +#pragma once + +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/awb.h" +#include "libipa/fixedpoint.h" + #include "algorithm.h" #include "ipa_context.h" +#include "params.h" namespace libcamera { namespace ipa::mali_c55::algorithms { +class MaliC55AwbStats; + class Awb : public Algorithm { public: - Awb(); ~Awb() = default; + int init(IPAContext &context, const ValueNode &tuningData) override; int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, MaliC55Params *params) override; @@ -29,10 +46,11 @@ public: ControlList &metadata) override; private: - void fillGainsParamBlock(MaliC55Params *params, - IPAContext &context, - IPAFrameContext &frameContext); void fillConfigParamBlock(MaliC55Params *params); + MaliC55AwbStats calculateRgbMeans(const IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats) const; + + AwbAlgorithm> awbAlgo_; }; } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/mali-c55/ipa_context.cpp b/src/ipa/mali-c55/ipa_context.cpp index 1b203e2b2605..74f1bd5323de 100644 --- a/src/ipa/mali-c55/ipa_context.cpp +++ b/src/ipa/mali-c55/ipa_context.cpp @@ -96,6 +96,9 @@ namespace libcamera::ipa::mali_c55 { * * \var IPAContext::frameContexts * \brief Ring buffer of per-frame contexts + * + * \var IPAContext::ctrlMap + * \brief A ControlInfoMap::Map of controls populated by the algorithms */ } /* namespace libcamera::ipa::mali_c55 */ diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index ac4b83773803..7dc765a3542b 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -14,6 +14,7 @@ #include +#include "libipa/awb.h" #include "libipa/fixedpoint.h" namespace libcamera { @@ -54,10 +55,7 @@ struct IPAActiveState { uint32_t temperatureK; } agc; - struct { - UQ<4, 8> rGain; - UQ<4, 8> bGain; - } awb; + ipa::awb::ActiveState awb; }; struct IPAFrameContext : public FrameContext { @@ -67,10 +65,7 @@ struct IPAFrameContext : public FrameContext { UQ<5, 8> ispGain; } agc; - struct { - UQ<4, 8> rGain; - UQ<4, 8> bGain; - } awb; + ipa::awb::FrameContext awb; }; struct IPAContext { From patchwork Wed Aug 5 16:12:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27626 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 0273FC3308 for ; Wed, 5 Aug 2026 16:13:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C16BB68139; Wed, 5 Aug 2026 18:13:30 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="I+w1YNr7"; 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 8FEEA68120 for ; Wed, 5 Aug 2026 18:13:26 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AE82C1387; Wed, 5 Aug 2026 18:12:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946336; bh=x1R8hRthh3rYjri7HEly1qTmNkVduv7wjFChXLwS9So=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=I+w1YNr7+ULodn7WJ64zhr5y1ylJ0O3Y7h4ONj3iV+6WGsLPLcEAqnOs6/WV7Abhk VNsNBKOrPfOviuoFK4/k3/ZOHpMHxneoJwu4t0VWDEJnimSi7p8dAxL28oGx1d2izt OfNXp0sGW04WIT46SbfnpyxlCvrAMGD+TVUlIEK0= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:45 +0200 Subject: [PATCH v7 03/32] ipa: simple: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-3-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham , Robert Mader X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=13652; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=WHDRo3uBCyTFkL94XGPm3wuTIA4erFvHsGWOOxztZHA=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EZljarcVv9/Hp5hiu227pev7AZwnfSLvUbv CV/OgYTWfyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhGQAKCRByNAaPFqFW PPXYEACDJ2aAOqR/TOMv6kluWXi63zN5mtfTue3wur0rfxGyIfewAZNmPnQGscfriZSsmxC1Z57 BrCaX0f3il0P0MrvKBem4ARjKThsmXEitarYeaZQBa9k4PHy6j0MzNa3Ry88VqridwgSzhr+YIK NKCuTpfpxi1kJ+xTecqKBYDz1E0SAo2W9UyXbedZdpvXvCCu5rjwzRDPwkNvfy9z1qEAr69UbrA D6WKbiCURFQHUJbuA6DpAwOk+nygU+MYSZ0FjOu8HrWpj3UGetOkBYQHzE3yEIR1Kzry53QqIHL ReuKLXyvhfM3tSMrhLmRpKyAQLkdQGdlGq5vSSWcqUXkfTNo4UA22+HX/A/qUZDNz06qbc2F8CG EiRxFBY2mNEBeMGKxs4bPLYGYH+z2mCdxzQXxuZQSaOBqWpXDT0RfZ/sf+eWAW1Aq7gYS/+7Dju yisS0mY3mLkjEF5erKSZNxbMq7QQv6Y2fmNdhuOKoWPS1py1MSk77nsOzhEAuzTY4afcoPaaGGT WrcOOn0KziYdON7jhVff4cOWabjW7hzv1NCWud5FT+c5MHKNqVUjqEXGuhYm+Cqs2ptcEO3yNDv a8gFhQyYlGCrmdA8pmzOEnPz9PvYGbM7iXeGu4pX5UwKEpgCp4XGpoRS0P6a/Fm0IboIN2n2Azz ZO2Fe5CRms8i8yA== 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" From: Kieran Bingham Port the SoftISP Awb algorithm to use the new libipa implementation of AwbAlgorithm. The awbAlgo_ class member is initialized with the Q<2, 8> type even if there is no physical register representation for SoftISP. The usage of libipa::awb::Context, which defines the gains vector as RGB, in the SimpleIPA ActiveState and FrameContext requires changes to debayer_cpu.cpp and blc.cpp in order not to mix RGB and RGB. Signed-off-by: Kieran Bingham Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Tested-by: Robert Mader Tested-by: Milan Zamazal Reviewed-by: Milan Zamazal --- v5->v6: - Remove [[maybe_unused]] from header - cast sum.r()/b()/g() to double before averaging - remove unused include for colors.h - remove \todo about means --- .../internal/software_isp/debayer_params.h | 4 +- src/ipa/simple/algorithms/awb.cpp | 140 +++++++++++++++------ src/ipa/simple/algorithms/awb.h | 28 +++++ src/ipa/simple/algorithms/blc.cpp | 2 +- src/ipa/simple/algorithms/ccm.cpp | 2 +- src/ipa/simple/ipa_context.h | 12 +- src/libcamera/software_isp/debayer_cpu.cpp | 12 +- 7 files changed, 145 insertions(+), 55 deletions(-) diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h index 6772b43bced4..1074720d73c2 100644 --- a/include/libcamera/internal/software_isp/debayer_params.h +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -21,10 +21,10 @@ struct DebayerParams { Matrix combinedMatrix = { { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 } }; - RGB blackLevel = RGB({ 0.0, 0.0, 0.0 }); + RGB blackLevel = RGB({ 0.0, 0.0, 0.0 }); float gamma = 1.0; float contrastExp = 1.0; - RGB gains = RGB({ 1.0, 1.0, 1.0 }); + RGB gains = RGB({ 1.0, 1.0, 1.0 }); }; } /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index 05155c83d172..0bd6b66ce859 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -14,50 +14,113 @@ #include -#include "libipa/colours.h" -#include "simple/ipa_context.h" - namespace libcamera { LOG_DEFINE_CATEGORY(IPASoftAwb) namespace ipa::soft::algorithms { +/* + * \todo Replace it with a proper Lux algorithm + */ +static constexpr unsigned int kDefaultLux = 500; + +class SimpleAwbStats final : public AwbStats +{ +public: + SimpleAwbStats() = default; + + SimpleAwbStats(const RGB &rgbMeans) + { + rgbMeans_ = rgbMeans; + + rg_ = rgbMeans_.r() / rgbMeans_.g(); + bg_ = rgbMeans_.b() / rgbMeans_.g(); + } + + double computeColourError(const RGB &gains) const override + { + /* + * Compute the sum of the squared colour error (non-greyness) as + * it appears in the log likelihood equation. + */ + double deltaR = gains.r() * rg_ - 1.0; + double deltaB = gains.b() * bg_ - 1.0; + double delta2 = deltaR * deltaR + deltaB * deltaB; + + return delta2; + } + + RGB rgbMeans() const override + { + return rgbMeans_; + } + + bool valid() const override + { + /* Minimum mean value below which AWB can't operate. */ + constexpr double minValue = 0.2; + + return rgbMeans_.r() > minValue || rgbMeans_.g() > minValue || + rgbMeans_.b() > minValue; + } + +private: + RGB rgbMeans_; + double rg_; + double bg_; +}; + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Awb::init(IPAContext &context, const ValueNode &tuningData) +{ + return awbAlgo_.init(tuningData, context.ctrlMap); +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ int Awb::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - auto &gains = context.activeState.awb.gains; - gains = { { 1.0, 1.0, 1.0 } }; + return awbAlgo_.configure(context.activeState.awb); +} - return 0; +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Awb::queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + awbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb, + controls); } +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ void Awb::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, DebayerParams *params) { - auto &gains = context.activeState.awb.gains; + awbAlgo_.prepare(context.activeState.awb, frameContext.awb); - frameContext.gains = gains; - params->gains = gains; + params->gains = frameContext.awb.gains; } -void Awb::process(IPAContext &context, - [[maybe_unused]] const uint32_t frame, - IPAFrameContext &frameContext, - const SwIspStats *stats, - ControlList &metadata) +SimpleAwbStats Awb::calculateRgbMeans(IPAContext &context, + const SwIspStats *stats) const { + if (!stats->valid) + return {}; + const SwIspStats::Histogram &histogram = stats->yHistogram; const uint8_t blackLevel = context.activeState.blc.level; - metadata.set(controls::ColourGains, { frameContext.gains.r(), - frameContext.gains.b() }); - - if (!stats->valid) - return; - /* * Black level must be subtracted to get the correct AWB ratios, they * would be off if they were computed from the whole brightness range @@ -67,30 +130,31 @@ void Awb::process(IPAContext &context, histogram.begin(), histogram.end(), uint64_t(0)); const uint64_t offset = blackLevel * nPixels; const uint64_t minValid = 1; + /* * Make sure the sums are at least minValid, while preventing unsigned * integer underflow. */ const RGB sum = stats->sum_.max(offset + minValid) - offset; - /* - * Calculate red and blue gains for AWB. - * Clamp max gain at 4.0, this also avoids 0 division. - */ - auto &gains = context.activeState.awb.gains; - gains = { { - sum.r() <= sum.g() / 4 ? 4.0f : static_cast(sum.g()) / sum.r(), - 1.0, - sum.b() <= sum.g() / 4 ? 4.0f : static_cast(sum.g()) / sum.b(), - } }; - - RGB rgbGains{ { 1 / gains.r(), 1 / gains.g(), 1 / gains.b() } }; - context.activeState.awb.temperatureK = estimateCCT(rgbGains); - metadata.set(controls::ColourTemperature, context.activeState.awb.temperatureK); - - LOG(IPASoftAwb, Debug) - << "gain R/B: " << gains << "; temperature: " - << context.activeState.awb.temperatureK; + RGB rgbMeans = { { static_cast(sum.r()) / nPixels, + static_cast(sum.g()) / nPixels, + static_cast(sum.b()) / nPixels } }; + + return SimpleAwbStats(rgbMeans); +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const SwIspStats *stats, + ControlList &metadata) +{ + SimpleAwbStats awbStats = calculateRgbMeans(context, stats); + + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + kDefaultLux, metadata); } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h index ad993f39c180..ff0a7a28e8f2 100644 --- a/src/ipa/simple/algorithms/awb.h +++ b/src/ipa/simple/algorithms/awb.h @@ -7,19 +7,36 @@ #pragma once +#include + +#include "libcamera/internal/software_isp/debayer_params.h" +#include "libcamera/internal/value_node.h" + +#include "libipa/awb.h" +#include "libipa/fixedpoint.h" +#include "simple/ipa_context.h" + #include "algorithm.h" namespace libcamera { namespace ipa::soft::algorithms { +class SimpleAwbStats; + class Awb : public Algorithm { public: Awb() = default; ~Awb() = default; + int init(IPAContext &context, + const ValueNode &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -29,6 +46,17 @@ public: IPAFrameContext &frameContext, const SwIspStats *stats, ControlList &metadata) override; + +private: + SimpleAwbStats calculateRgbMeans(IPAContext &context, + const SwIspStats *stats) const; + + /* + * There actually is no Q register format for SoftISP, but allow the + * colour gains to range in the [0.0f, 3.999f] interval, which seems + * reasonable. + */ + AwbAlgorithm> awbAlgo_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp index 677be56ed669..e45a913cd970 100644 --- a/src/ipa/simple/algorithms/blc.cpp +++ b/src/ipa/simple/algorithms/blc.cpp @@ -53,7 +53,7 @@ void BlackLevel::prepare(IPAContext &context, DebayerParams *params) { /* Latch the blacklevel gain so GPUISP can apply. */ - params->blackLevel = RGB(context.activeState.blc.level / 255.0f); + params->blackLevel = RGB(context.activeState.blc.level / 255.0f); } void BlackLevel::process(IPAContext &context, diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index ace9c35dc462..1174784edc7e 100644 --- a/src/ipa/simple/algorithms/ccm.cpp +++ b/src/ipa/simple/algorithms/ccm.cpp @@ -44,7 +44,7 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) { - const unsigned int ct = context.activeState.awb.temperatureK; + const unsigned int ct = frameContext.awb.colourTemperature; /* Change CCM only on bigger temperature changes. */ if (!currentCcm_ || diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 8ccfacb46a59..29643a655ce1 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -16,6 +16,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" +#include #include #include "core_ipa_interface.h" @@ -36,6 +37,8 @@ struct IPASessionConfiguration { }; struct IPAActiveState { + ipa::awb::ActiveState awb; + struct { int32_t exposure; double again; @@ -48,11 +51,6 @@ struct IPAActiveState { double lastGain; } blc; - struct { - RGB gains; - unsigned int temperatureK; - } awb; - Matrix combinedMatrix; struct { @@ -64,6 +62,8 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { + ipa::awb::FrameContext awb; + Matrix ccm; struct { @@ -71,8 +71,6 @@ struct IPAFrameContext : public FrameContext { double gain; } sensor; - RGB gains; - float gamma; std::optional contrast; std::optional saturation; diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp index 25e9830c8a16..c6d5d1e1813b 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -987,9 +987,9 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) }; const unsigned int gammaTableSize = gammaTable_.size(); - const RGB blackIndex = params.blackLevel * kRGBLookupSize; - const RGB gains = params.gains; - const RGB div = (RGB(kRGBLookupSize) - blackIndex).max(1.0); + const RGB blackIndex = params.blackLevel * kRGBLookupSize; + const RGB gains = params.gains; + const RGB div = (RGB(kRGBLookupSize) - blackIndex).max(1.0); if (ccmEnabled_) { if (gammaUpdateNeeded || @@ -1002,7 +1002,7 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) const unsigned int greenIndex = 1; const unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2; for (unsigned int i = 0; i < kRGBLookupSize; i++) { - const RGB rgb = (gains * (RGB(i) - blackIndex) * kRGBLookupSize / div) + const RGB rgb = (gains * (RGB(i) - blackIndex) * kRGBLookupSize / div) .clamp(0.0, kRGBLookupSize - 1); red[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][0]); red[i].g = std::round(rgb.r() * params.combinedMatrix[greenIndex][0]); @@ -1022,8 +1022,8 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) auto &green = green_; auto &blue = swapRedBlueGains_ ? red_ : blue_; for (unsigned int i = 0; i < kRGBLookupSize; i++) { - const RGB lutGains = - (gains * (RGB(i) - blackIndex) * gammaTableSize / div) + const RGB lutGains = + (gains * (RGB(i) - blackIndex) * gammaTableSize / div) .clamp(0.0, gammaTableSize - 1); red[i] = gammaTable_[lutGains.r()]; green[i] = gammaTable_[lutGains.g()]; From patchwork Wed Aug 5 16:12:46 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27627 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 4EF51C3336 for ; Wed, 5 Aug 2026 16:13:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C4E0468130; Wed, 5 Aug 2026 18:13:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="d4VJTbGs"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4927168126 for ; Wed, 5 Aug 2026 18:13:27 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 623D31C76; Wed, 5 Aug 2026 18:12:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946336; bh=R7FpN/mH4/C5DorQm3s1dDcX6F4djXNC3Su7Cpi97xU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=d4VJTbGsBgDR0wi1ZPAOnfPK3Rl2TNx2IWRr58VaxZ1ISG57M7VRAtxKz/F5XPo7j sNLeREJJ92m1/LBRUE8OoletewTggahQ1cZkqCfvQF2O99ZjmHPIU+RYhhPdRBnRwS Jj06hgQWQqMTCjSQyAFEMw3rQIA1RW1folsaj88Y= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:46 +0200 Subject: [PATCH v7 04/32] ipa: libipa: Add CcmAlgorithm to libipa MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-4-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally , Robert Mader X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=12018; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=R7FpN/mH4/C5DorQm3s1dDcX6F4djXNC3Su7Cpi97xU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EZUiupcPbx3QSUi3VHSNN7anLlHkFWZWeLW WjDqVLf/GiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhGQAKCRByNAaPFqFW PF46EADAqtG1g38Iemh+HuwQFaBVYHvHbkHfYW+krBorUYo/HXwNI7zZvtIatSp3wq3AaNh2tlr qlW38cPa5263152+Qk1HqzLAc+HKFG6JHiQVXLIOzWXNXlXzJwcWkCkdLdB01w1z8ZZB5+uIZ/9 fN8LpNMbSakuGK5N7cvQrVvWm1ihbBNUi77cWiPHtSGlAIrpGhDHPIZaOwYAnk5BBpP9fC8qh5A 0GjTDpbCJCS4ffnTvTC4qAXuR+3QqtGXm97/SBtBuRFwid5dp/eyOv5y/iiRcX1EHqjucbMdApu vXmeasQI7dkUNtGcvLGnSwswIdPyxePpDDOI4MrP7tZvBWJ2MdaMZYTYzTRNmU6zp+H8BhrlGuO ITIqSgb/EA1iBPvz7EW32XU10CBD9xy9x6w0TzjflrpOSDtSs7XedutIbD8Q/8ViEKkPZb2BMaq FGfJAfWH7H1k4gV4BBFaPBGPYbspgMDzUw4qeLm4GDAFVcPWLXaEqcqcKdPbubfbGuhBbZd/TKE uXEBlhnkWCSiOZwppCCLeWjqqseXf98sm8yZvxmAYNU2f+q/Tt//hzf/8pClzsJ8replx8pSY+F AKQ4lWaOqZzW7lKTdqcjCL56ARTF7Ds+TgvsIsOok8SEDnL9Z8Gh4OxLQVw3YI3fwy6qpXUm4aO mfb6iIiWMvnCrMw== 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 Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally Tested-by: Robert Mader --- src/ipa/libipa/ccm.cpp | 245 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/ccm.h | 77 ++++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 324 insertions(+) diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp new file mode 100644 index 000000000000..8f3652f7e32b --- /dev/null +++ b/src/ipa/libipa/ccm.cpp @@ -0,0 +1,245 @@ +/* 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 CcmContext + * \brief CCM coefficients and offsets + * + * \var CcmContext::ccm + * \brief Matrix of 3x3 CCM coefficients + * + * \var CcmContext::offsets + * \brief Vector of RGB CCM offsets + */ + +/** + * \typedef FrameContext + * \brief Per-frame CCM state + */ + +} /* 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 associated with 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 given 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 = state.manual; +} + +/** + * \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 = state.automatic; + return; + } + + ct_ = temperatureK; + context.ccm = ccm_.getInterpolated(ct_); + context.offsets = offsets_.getInterpolated(ct_); + + state.automatic = context; +} + +/** + * \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()); +} + +/** + * \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 implements 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 functions in their implementations of the + * Algorithm interface. + * + * The CcmAlgorithm class provides an init() function where tuning data is + * 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 IPA module 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 prepare(). 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 prepare(). + * + * When the IPA module runs in automatic mode instead, it estimates the scene + * colour temperature. The estimated colour temperature shall be passed to + * prepare(), 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..b72b25e2a3b3 --- /dev/null +++ b/src/ipa/libipa/ccm.h @@ -0,0 +1,77 @@ +/* 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 CcmContext { + Matrix ccm; + Matrix offsets; +}; + +struct ActiveState { + struct CcmContext manual; + struct CcmContext automatic; +}; + +using FrameContext = CcmContext; + +} /* 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); + +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; + + controls[&controls::ColourCorrectionMatrix] = + ControlInfo(ControlValue(Q::TraitsType::min), + ControlValue(Q::TraitsType::max), + 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 16f4b095f220..635abad778e6 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', @@ -29,6 +30,7 @@ libipa_sources = files([ 'awb_grey.cpp', 'awb.cpp', 'camera_sensor_helper.cpp', + 'ccm.cpp', 'colours.cpp', 'exposure_mode_helper.cpp', 'fc_queue.cpp', From patchwork Wed Aug 5 16:12:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27628 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 904FEC3338 for ; Wed, 5 Aug 2026 16:13:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8FF4268138; Wed, 5 Aug 2026 18:13:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jyMXVFA0"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E2B8868117 for ; Wed, 5 Aug 2026 18:13:27 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1866B23B9; Wed, 5 Aug 2026 18:12:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946337; bh=ltIYhnnM1bUd815aMZ307m3Gl3PwVM2zbZHeIKk9o7s=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=jyMXVFA0YrB/u+lwE2Y6ypNch4O6eI0mM1YC5cgGWh9HTjR66oBr5G3kplemhSBRN 4BfQHWOqw0+NPLHjzyqMovFuF24W75yxEQtw0HQc25q0OD8KZsuIc1c1CoYXwrxXg/ CmtxP5U/0whAUGFsQZWdb4A9pfBaQPq+xO9PK3ug= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:47 +0200 Subject: [PATCH v7 05/32] ipa: rkisp1: ccm: Port to use CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-5-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=8073; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ltIYhnnM1bUd815aMZ307m3Gl3PwVM2zbZHeIKk9o7s=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EaKLxFgPsmJQpQqm87F7SIuaYvayURiIBa5 iT2/kNo6CyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhGgAKCRByNAaPFqFW PF4HEACADcQNMYzCRJuFs5FibSQovfW+/sGqxYd6pkGk2xtzgl59JTiaXbH4OU+YF21pmSEwyg/ WETUXNpGLzuiezQFVGLRzdc80cfqACL0MUxicxEkMr3oyUtrhqiagTNskAREwD0tyGw0X7At3by LYNpVrU+wzZMzXL2WWfmviWP2I5snHyaF7R7j2V5/x705kjIQZNJgns37NAOfs9g/rUmXqZoB4z vZjdCAdkVV63ZNUg/NeuG2CIxfqg3aK1LA1Rv/ShRcSffK10UoTcZvnM5wCY3FGAavDOlOr8Inx Fa8Mz36XJOZe46F/c3aj8YeaiUTszUq1HqhwXRKeKa1k/u/4Q1V96zRAKlEihG9m+q8hMARouuT L0hlWkFayPN9SZ6ncjQ74TWdem0Oq4BjkU3213z+W5zHzPfmYvY0tdLc3uD1ruLZetFAjTh/Rrz 6Blv1xtlb3ISav5JFYuc5OnFnVSrkp1VoIssKGWXvPvejg+D3DzdNyZH2Ktk+CIg4NKS0J+Jtli Lb9ZzZBuGRCgep7aV9M42Kcn/p79Wqswy8uoad+qsIyYNjgSkxIWQrhZc7joXKKS9ZUcgenn7VD 0YoAuvXDV+JymoLD2z76+e84ER80CPidBaXShj7H/cJxXpf6RNfXYKvCLjw9XgbJRuPvLp/ymx/ pZ2MDQ6U+PtPpFw== 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" Port the Ccm RkISP1 implementation to use the libipa CcmAlgorithm class. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally --- src/ipa/rkisp1/algorithms/ccm.cpp | 97 ++++++++------------------------------- src/ipa/rkisp1/algorithms/ccm.h | 17 +++---- src/ipa/rkisp1/ipa_context.h | 10 ++-- 3 files changed, 32 insertions(+), 92 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp index 633dc2ddb834..0e54c987104d 100644 --- a/src/ipa/rkisp1/algorithms/ccm.cpp +++ b/src/ipa/rkisp1/algorithms/ccm.cpp @@ -16,13 +16,11 @@ #include -#include "libcamera/internal/value_node.h" - -#include "libipa/fixedpoint.h" #include "libipa/interpolator.h" /** * \file ccm.h + * \brief RkISP1 CCM algorithm implementation */ namespace libcamera { @@ -31,42 +29,17 @@ namespace ipa::rkisp1::algorithms { /** * \class Ccm - * \brief A color correction matrix algorithm + * \brief RkISP1 color correction matrix algorithm */ LOG_DEFINE_CATEGORY(RkISP1Ccm) -constexpr Matrix kIdentity3x3 = Matrix::identity(); - /** * \copydoc libcamera::ipa::Algorithm::init */ int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) { - auto &cmap = context.ctrlMap; - cmap[&controls::ColourCorrectionMatrix] = ControlInfo( - ControlValue(-8.0f), - ControlValue(7.993f), - ControlValue(kIdentity3x3.data())); - - int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); - if (ret < 0) { - LOG(RkISP1Ccm, Warning) - << "Failed to parse 'ccm' " - << "parameter from tuning file; falling back to unit matrix"; - ccm_.setData({ { 0, kIdentity3x3 } }); - } - - ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); - if (ret < 0) { - LOG(RkISP1Ccm, Warning) - << "Failed to parse 'offsets' " - << "parameter from tuning file; falling back to zero offsets"; - - offsets_.setData({ { 0, Matrix({ 0, 0, 0 }) } }); - } - - return 0; + return ccmAlgo_.init(tuningData, context.ctrlMap); } /** @@ -75,10 +48,8 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) int Ccm::configure(IPAContext &context, [[maybe_unused]] const IPACameraSensorInfo &configInfo) { - auto &as = context.activeState; - as.ccm.manual = kIdentity3x3; - as.ccm.automatic = ccm_.getInterpolated(as.awb.automatic.colourTemperature); - return 0; + return ccmAlgo_.configure(context.activeState.ccm, + context.activeState.awb.automatic.colourTemperature); } void Ccm::queueRequest(IPAContext &context, @@ -90,38 +61,28 @@ void Ccm::queueRequest(IPAContext &context, if (frameContext.awb.autoEnabled) return; - auto &ccm = context.activeState.ccm; - - const auto &colourTemperature = controls.get(controls::ColourTemperature); - const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); - if (ccmMatrix) { - ccm.manual = Matrix(*ccmMatrix); - LOG(RkISP1Ccm, Debug) - << "Setting manual CCM from CCM control to " << ccm.manual; - } else if (colourTemperature) { - ccm.manual = ccm_.getInterpolated(*colourTemperature); - LOG(RkISP1Ccm, Debug) - << "Setting manual CCM from CT control to " << ccm.manual; - } - - frameContext.ccm.ccm = ccm.manual; + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); } -void Ccm::setParameters(struct rkisp1_cif_isp_ctk_config &config, - const Matrix &matrix, - const Matrix &offsets) +void Ccm::setParameters(RkISP1Params *params, IPAFrameContext &context) { + const Matrix &matrix = context.ccm.ccm; + const Matrix &offsets = context.ccm.offsets; + + auto config = params->block(); + config.setEnabled(true); + /* * 4 bit integer and 7 bit fractional, ranging from -8 (0x400) to * +7.9921875 (0x3ff) */ for (unsigned int i = 0; i < 3; i++) { for (unsigned int j = 0; j < 3; j++) - config.coeff[i][j] = Q<4, 7>(matrix[i][j]).quantized(); + config->coeff[i][j] = Q<4, 7>(matrix[i][j]).quantized(); } for (unsigned int i = 0; i < 3; i++) - config.ct_offset[i] = offsets[i][0] & 0xfff; + config->ct_offset[i] = offsets[i][0] & 0xfff; LOG(RkISP1Ccm, Debug) << "Setting matrix " << matrix; LOG(RkISP1Ccm, Debug) << "Setting offsets " << offsets; @@ -133,29 +94,11 @@ void Ccm::setParameters(struct rkisp1_cif_isp_ctk_config &config, void Ccm::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - if (!frameContext.awb.autoEnabled) { - auto config = params->block(); - config.setEnabled(true); - setParameters(*config, frameContext.ccm.ccm, Matrix()); - return; - } - - uint32_t ct = frameContext.awb.colourTemperature; - if (frame > 0 && ct == ct_) { - frameContext.ccm.ccm = context.activeState.ccm.automatic; - return; - } - - ct_ = ct; - Matrix ccm = ccm_.getInterpolated(ct); - Matrix offsets = offsets_.getInterpolated(ct); - - context.activeState.ccm.automatic = ccm; - frameContext.ccm.ccm = ccm; + if (frameContext.awb.autoEnabled) + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, + frame, frameContext.awb.colourTemperature); - auto config = params->block(); - config.setEnabled(true); - setParameters(*config, ccm, offsets); + setParameters(params, frameContext); } /** @@ -167,7 +110,7 @@ void Ccm::process([[maybe_unused]] IPAContext &context, [[maybe_unused]] const rkisp1_stat_buffer *stats, ControlList &metadata) { - metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data()); + ccmAlgo_.process(frameContext.ccm, metadata); } REGISTER_IPA_ALGORITHM(Ccm, "Ccm") diff --git a/src/ipa/rkisp1/algorithms/ccm.h b/src/ipa/rkisp1/algorithms/ccm.h index 3d3a660065ff..6689c42092f3 100644 --- a/src/ipa/rkisp1/algorithms/ccm.h +++ b/src/ipa/rkisp1/algorithms/ccm.h @@ -9,11 +9,16 @@ #include -#include "libcamera/internal/matrix.h" +#include -#include "libipa/interpolator.h" +#include "libcamera/internal/value_node.h" + +#include "libipa/ccm.h" +#include "libipa/fixedpoint.h" #include "algorithm.h" +#include "ipa_context.h" +#include "params.h" namespace libcamera { @@ -41,13 +46,9 @@ public: ControlList &metadata) override; private: - void setParameters(struct rkisp1_cif_isp_ctk_config &config, - const Matrix &matrix, - const Matrix &offsets); + void setParameters(RkISP1Params *params, IPAFrameContext &context); - unsigned int ct_; - Interpolator> ccm_; - Interpolator> offsets_; + CcmAlgorithm> ccmAlgo_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index c36c1f7e0084..6698ccfd77cf 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -27,6 +27,7 @@ #include "libipa/agc_mean_luminance.h" #include "libipa/awb.h" #include "libipa/camera_sensor_helper.h" +#include "libipa/ccm.h" #include "libipa/fc_queue.h" #include "libipa/fixedpoint.h" @@ -105,10 +106,7 @@ struct IPAActiveState { ipa::awb::ActiveState awb; - struct { - Matrix manual; - Matrix automatic; - } ccm; + ipa::ccm::ActiveState ccm; struct { float requestedBrightness; @@ -207,9 +205,7 @@ struct IPAFrameContext : public FrameContext { double gain; } sensor; - struct { - Matrix ccm; - } ccm; + ipa::ccm::FrameContext ccm; struct { double lux; From patchwork Wed Aug 5 16:12:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27629 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 E1E8CC3339 for ; Wed, 5 Aug 2026 16:13:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3F0B16814A; Wed, 5 Aug 2026 18:13:33 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="HHiRpgzA"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9704A68117 for ; Wed, 5 Aug 2026 18:13:28 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B3FE95B3; Wed, 5 Aug 2026 18:12:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946338; bh=PZjf6FQi9gCzmouGinDVKkg+erELkT3B1ZUh0u8Sa2k=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=HHiRpgzA7vjuk3uxPHcMC4FWMgpjy9sshtSlaDWeSqH7ZCKgFHXLTGsjl/a+59DxS tWmIsyBUSo9/1eQxijnu0nqVVegW9vjxP3Py4TJkha674YzA8/H8E8dV2japqOaFUj ks2qYKjvVSZCYT4T0gsjMQCF82G0H4xqbjLzk60Q= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:48 +0200 Subject: [PATCH v7 06/32] ipa: simple: Port to use libipa CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-6-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham , Robert Mader X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=6404; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=f2e+ElLswiS5th0nFoFYfobTzlHF8UV7oPYZ1lTpAwc=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EaIBwwUDj/01wfuOYEaZURTkjtrWr5h/yHt PHTjCo+1aiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhGgAKCRByNAaPFqFW PFXcD/0ZVychiS8FrKcYWCUcobUDbYQi5wK6R6LNL4AYsbpxoMW+XcqzELcF3DX9kp/roLcYVTs VX41FA98dIpzFhlHnoSLWsrb6KblMdQ37WOEt6HWI1kjS44AtE7W84wGyqgNlJSDIJB3wxF4rBM koglMCwqHIladAZfcqKOaLGBjQpPLxUSbgys6T9GrJ6fvSKlCo8LqxL5NDswaU7PwK0E+boqUft lzhx7MUMGjsY81f8+7JCyjcoR1/CEJsXQjaxIadJGQPzi+Ai2EyU0+vF14vQ3/Nv8yq4GMtKQVg ZeEZQQzlVBbzDnecmzxKsMqHigzrFNvH1rRCDM+H6UzGIeixlbMjXQJ2Sotikd7oQMgYsdfpn2W zW+uxpppSM1/vXC7r6zYpXToSAgkH5P1yG655Qo1PJODleGXJgXPQYGP5CQ0zEO1Is+pl3f+bd4 0yre0FSD8ZYOvJ9aWC4oLoj+mBb6ouTq3IL3ZQUgPqrE7AahuDFYRTLGfGtQIYTg+YhRsjaeFiL gzGDUjy1f5p58J0QWJ2Yco18CnVOgPjhFZr3XrS8v/FExZ/IcByb5Mr9cfZXIAq1j26YRe4ZDfO keayTm8DOur4HtXyYpAE0QXbVnsINuInv3g44UHS6RArbSZT6DQr3pyRVrrpboKrWt2AqgONGig JjIxulFO712U6NA== 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" From: Kieran Bingham Now that libipa provides a common CCM algorithm implementation, replace the custom handling with the common Ccm. Signed-off-by: Kieran Bingham Signed-off-by: Jacopo Mondi Tested-by: Robert Mader Reviewed-by: Milan Zamazal Tested-by: Milan Zamazal --- v5->v6: - Remove a few [[maybe_unused]] - Add Milan's tag - Reword title to match other patches in the series --- src/ipa/simple/algorithms/ccm.cpp | 74 ++++++++++++++++++++++----------------- src/ipa/simple/algorithms/ccm.h | 22 +++++++----- src/ipa/simple/ipa_context.h | 5 +-- 3 files changed, 58 insertions(+), 43 deletions(-) diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index 1174784edc7e..8b6da543a719 100644 --- a/src/ipa/simple/algorithms/ccm.cpp +++ b/src/ipa/simple/algorithms/ccm.cpp @@ -8,54 +8,64 @@ #include "ccm.h" -#include -#include - -#include - #include "libcamera/internal/matrix.h" -namespace { - -constexpr unsigned int kTemperatureThreshold = 100; - -} - namespace libcamera { namespace ipa::soft::algorithms { LOG_DEFINE_CATEGORY(IPASoftCcm) -int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Ccm::init(IPAContext &context, const ValueNode &tuningData) { - int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); - if (ret < 0) { - LOG(IPASoftCcm, Error) - << "Failed to parse 'ccm' parameter from tuning file."; - return ret; - } - + /* Informs the 'adjust' component that CCM is available to apply Saturation */ context.ccmEnabled = true; - return 0; + return ccmAlgo_.init(tuningData, context.ctrlMap); } -void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, - IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Ccm::configure(IPAContext &context, + [[maybe_unused]] const IPAConfigInfo &configInfo) +{ + return ccmAlgo_.configure(context.activeState.ccm, + context.activeState.awb.automatic.colourTemperature); +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Ccm::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) { - const unsigned int ct = frameContext.awb.colourTemperature; + /* Nothing to do here, the ccm will be calculated in prepare() */ + if (frameContext.awb.autoEnabled) + return; - /* Change CCM only on bigger temperature changes. */ - if (!currentCcm_ || - utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) { - currentCcm_ = ccm_.getInterpolated(ct); - lastCt_ = ct; - } + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); +} +void Ccm::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) +{ + if (frameContext.awb.autoEnabled) + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, + frame, frameContext.awb.colourTemperature); + + /* + * \todo: Split out combined matrix into individual parameters in + * DebayerParams and perform any pre-multiplication combination in the + * SoftISP component directly. + */ context.activeState.combinedMatrix = - currentCcm_.value() * context.activeState.combinedMatrix; - frameContext.ccm = currentCcm_.value(); + frameContext.ccm.ccm * context.activeState.combinedMatrix; } void Ccm::process([[maybe_unused]] IPAContext &context, @@ -64,7 +74,7 @@ void Ccm::process([[maybe_unused]] IPAContext &context, [[maybe_unused]] const SwIspStats *stats, ControlList &metadata) { - metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.data()); + ccmAlgo_.process(frameContext.ccm, metadata); } REGISTER_IPA_ALGORITHM(Ccm, "Ccm") diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h index b20a7da8aa33..27f11bcbd6e9 100644 --- a/src/ipa/simple/algorithms/ccm.h +++ b/src/ipa/simple/algorithms/ccm.h @@ -7,13 +7,15 @@ #pragma once -#include +#include -#include "libcamera/internal/matrix.h" +#include "libcamera/internal/value_node.h" -#include +#include "libipa/ccm.h" +#include "libipa/fixedpoint.h" #include "algorithm.h" +#include "ipa_context.h" namespace libcamera { @@ -22,10 +24,14 @@ namespace ipa::soft::algorithms { class Ccm : public Algorithm { public: - Ccm() = default; - ~Ccm() = default; - int init(IPAContext &context, const ValueNode &tuningData) override; + + int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -36,9 +42,7 @@ public: ControlList &metadata) override; private: - unsigned int lastCt_; - Interpolator> ccm_; - std::optional> currentCcm_; + CcmAlgorithm> ccmAlgo_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 29643a655ce1..ff312ae8f4e7 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -17,6 +17,7 @@ #include "libcamera/internal/vector.h" #include +#include #include #include "core_ipa_interface.h" @@ -38,6 +39,7 @@ struct IPASessionConfiguration { struct IPAActiveState { ipa::awb::ActiveState awb; + ipa::ccm::ActiveState ccm; struct { int32_t exposure; @@ -63,8 +65,7 @@ struct IPAActiveState { struct IPAFrameContext : public FrameContext { ipa::awb::FrameContext awb; - - Matrix ccm; + ipa::ccm::FrameContext ccm; struct { int32_t exposure; From patchwork Wed Aug 5 16:12:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27630 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 57E4BC333A for ; Wed, 5 Aug 2026 16:13:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4FAD568136; Wed, 5 Aug 2026 18:13:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="etD3w4Pu"; 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 4491868133 for ; Wed, 5 Aug 2026 18:13:29 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 66AA6245A; Wed, 5 Aug 2026 18:12:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946338; bh=4A9kajOFuKXSQXUsoFZ+WfGwjzpqM6yrrwEjlszpxEw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=etD3w4PucSM19iQV/pYmRFyWIJIgui5rhzOrwOOH/sXE90um+JgnVc9Q/B5Wq60U4 3DxqwzTO5ul7F1a5ViVs4NnmoakaDEuUfB3USUqXRvsFidz5TDzqH6bogSAsqT9WpH BSir5JEsICIsabAJnAvcHMFHEhKqbTOP1XzhmidA= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:49 +0200 Subject: [PATCH v7 07/32] include: linux: mali-c55: Update to support CCM and Gamma MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-7-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Daniel Scally , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=5761; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=4A9kajOFuKXSQXUsoFZ+WfGwjzpqM6yrrwEjlszpxEw=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EbV6nQUl365kjHzBGN2Dr+VezHCljWO49c2 n892LI7NeeJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhGwAKCRByNAaPFqFW PCT+EAC+kVPM3RNPDFYmPjRKL4Y7eZojpQdl4Pw8JVqzPdgI2UfITOBiAoRzvBXoEAKRlA7xC4m GXcsUjkz1O5+E2pf+4rspFt0mJjzs6nxwHHhsgVj3hJIddZDl4+k6tr33B6jzvZ36E5hmaGyZbd kxviuzwadTPfKqva/iFa3+NcZwYw1LBVBMjj2W6ltRMrbyxwNfsLzpikcpkgT+SfXuKGF2ZddnL 52clUkSZdfgvBnxemIG2BpT1IjtsTZ6pFo55JDYmGgUEWMmNX+FCi3JAs1MufdmTUWpcfnJpuxI B8Yktz0uKK3A5/1Y2GW+Kz45Mhg1RTeAl2o1qdJA1Ykxeo3QpDip0pdSqTCqsKSnfXjntwCiH10 yD25BWF89tvll1Rs0puIiYwzIEvwkcFvmLfqK9Wyq2TOXUUGNyiszi6g6YSI/plaHCioaqWUDKE upL4pG1AL4vzpJVau0l92v+QorBTWjS2Z6PpsWojADTiwQq+QDw1KzzW629tANezdpQ0hEieEzQ LdEi0CcquVEpG6n53NdDz8XFX0rKnQhdxyRwruH7cDD9kP1ebf3kVPGH4lpopZwxnKnMNYEoxyE u7b7CQ9W0yDzWIUgcXqwzRTsP7fp+pBvOjG8coCb8kxmljEvbQdT+XvYu4U7GXCJAb7Zou1wA3B X3VIRD8MbnDMe+A== 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" Update the Mali-C55 uAPI header to add support for CCM and the RGB Gamma. Patches have been posted upstream but not yet merged https://patchwork.linuxtv.org/project/linux-media/list/?series=26932 Signed-off-by: Jacopo Mondi Reviewed-by: Daniel Scally Acked-by: Kieran Bingham --- include/linux/media/arm/mali-c55-config.h | 86 ++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/include/linux/media/arm/mali-c55-config.h b/include/linux/media/arm/mali-c55-config.h index 3d335f950eeb..ef0fe17df96e 100644 --- a/include/linux/media/arm/mali-c55-config.h +++ b/include/linux/media/arm/mali-c55-config.h @@ -36,6 +36,9 @@ */ #define MALI_C55_MAX_ZONES (15 * 15) +/* Number of RGB gamma LUT entries. */ +#define MALI_C55_NUM_GAMMA_LUT_ELEMENTS 129 + /** * struct mali_c55_ae_1024bin_hist - Auto Exposure 1024-bin histogram statistics * @@ -219,6 +222,9 @@ struct mali_c55_stats_buffer { * @MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP: Auto-white balance gains for AEXP-0 tap * @MALI_C55_PARAM_MESH_SHADING_CONFIG : Mesh shading tables configuration * @MALI_C55_PARAM_MESH_SHADING_SELECTION: Mesh shading table selection + * @MALI_C55_PARAM_BLOCK_CCM: Colour correction matrix + * @MALI_C55_PARAM_BLOCK_GAMMA_FR: Gamma gain and offset for FR pipe + * @MALI_C55_PARAM_BLOCK_GAMMA_DS: Gamma gain and offset for DS pipe */ enum mali_c55_param_block_type { MALI_C55_PARAM_BLOCK_SENSOR_OFFS, @@ -232,6 +238,9 @@ enum mali_c55_param_block_type { MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP, MALI_C55_PARAM_MESH_SHADING_CONFIG, MALI_C55_PARAM_MESH_SHADING_SELECTION, + MALI_C55_PARAM_BLOCK_CCM, + MALI_C55_PARAM_BLOCK_GAMMA_FR, + MALI_C55_PARAM_BLOCK_GAMMA_DS, }; /** @@ -757,6 +766,78 @@ struct mali_c55_params_mesh_shading_selection { __u16 mesh_strength; }; +/** + * struct mali_c55_params_ccm - Coefficients, offsets and gains for the colour + * correction matrix + * + * The colour correction module converts images data from a sensor-specific + * colour space to known one. + * + * Colour correction is applied after demosaicing and each pixel is represented + * as a column vector of the three RGB colour channels on which the following + * operations take place: + * 1) An offset is subtracted from each colour channel + * 2) Each colour channel is multiplied by a gain + * 3) The pixel column vector is multiplied by the colour correction matrix + * + * This struct allows users to configure the coefficients for CCM and the + * per-channel offsets and gains. The nine matrix coefficients are expressed as + * 13 bits signed Q4.8 Sign/Magnitude fixed-point numbers, the three gain + * multipliers are expressed as 12 bits unsigned Q4.8 fixed-point numbers and + * the three offsets are expressed as a 12 bits unsigned integers. + * + * header.type should be set to MALI_C55_PARAM_BLOCK_CCM from + * :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @coeffs: 3x3 color conversion matrix coefficients in sign/magnitude + * Q4.8 format + * @gains: Gains for red, green and blue channels in unsigned Q4.8 format + * @offs: Offsets for red, green and blue channels + */ +struct mali_c55_params_ccm { + struct v4l2_isp_params_block_header header; + __u16 coeffs[3][3]; + __u16 gains[3]; + __u16 offs[3]; +}; + +/** + * struct mali_c55_params_gamma - RGB Gamma correction + * + * Gamma correction is used to program a standard gamma curve such as the sRGB + * one. It provides gains and offsets to implement contrast adjustments. + * + * Gamma correction is applied on both the FR and DS pipes separately in the RGB + * colour domain where the following operations take place: + * 1) An offset is subtracted from each colour channel + * 2) Each colour channel is multiplied by a gain + * 3) The Gamma LUT is applied to each colour channel + * + * The Gamma LUT has 129 entries where each node is an unsigned 12 bit number. + * It is expected that LUT[0]=0 and LUT[128]=0xfff, with the other 127 values + * defining the Gamma correction curve. The three gain multipliers are expressed + * as 12-bits unsigned Q4.8 fixed-point numbers and the three offsets are + * expressed as a 12-bits unsigned integers. + * + * As one Gamma correction block is available on both the FR and DS pipes, the + * header.type field should be set to one of either + * MALI_C55_PARAM_BLOCK_GAMMA_FR or MALI_C55_PARAM_BLOCK_GAMMA_DS from + * :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @gains: Gains for the red, green and blue channel in unsigned Q4.8 format + * @offs: Offsets subtracted from the red, green and blue channels + * in unsigned 12 bits format + * @lut: 129-node Gamma LUT in unsigned 12 bits format + */ +struct mali_c55_params_gamma { + struct v4l2_isp_params_block_header header; + __u16 gains[3]; + __u16 offs[3]; + __u32 lut[MALI_C55_NUM_GAMMA_LUT_ELEMENTS]; +}; + /** * define MALI_C55_PARAMS_MAX_SIZE - Maximum size of all Mali C55 Parameters * @@ -780,6 +861,9 @@ struct mali_c55_params_mesh_shading_selection { sizeof(struct mali_c55_params_awb_config) + \ sizeof(struct mali_c55_params_awb_gains) + \ sizeof(struct mali_c55_params_mesh_shading_config) + \ - sizeof(struct mali_c55_params_mesh_shading_selection)) + sizeof(struct mali_c55_params_mesh_shading_selection) + \ + sizeof(struct mali_c55_params_ccm) + \ + sizeof(struct mali_c55_params_gamma) + \ + sizeof(struct mali_c55_params_gamma)) #endif /* __UAPI_MALI_C55_CONFIG_H */ From patchwork Wed Aug 5 16:12:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27631 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 6907DC333B for ; Wed, 5 Aug 2026 16:13:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 282A768154; Wed, 5 Aug 2026 18:13:36 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="C4jWUnXO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EA17D6811F for ; Wed, 5 Aug 2026 18:13:29 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1D4DE2B5C; Wed, 5 Aug 2026 18:12:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946339; bh=SOOy6+b1C5ELyjZe7grsoEYDR364OsGEhvltzEaa+NA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=C4jWUnXOaBQugDGLTovtdhRWxX0DEaxdpCwnJT/0miR0Bfe0Nm2rgBf963bXQ2cnT od8A7ugMFD5J3ltbPAE762aNnDA9THMywc3MQWkiJKyzzpqT4t0/N1v0PutIrNAc6j yxTnJy0DzZZSse8BAy3e3kDQHE3VNBA7BL6/vCR8= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:50 +0200 Subject: [PATCH v7 08/32] ipa: mali-c55: Implement Ccm algorithm MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-8-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=9962; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=SOOy6+b1C5ELyjZe7grsoEYDR364OsGEhvltzEaa+NA=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EbOw0rdCQrUVqBa6X5n4fuvbzw8U9Ic5ku8 DBGsAv0zNmJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhGwAKCRByNAaPFqFW PM75D/0ZQUZVkLOxCFt+Spu16OuU3s5VSBB/1/qU1WmS/uI0dDqHkGCuVYKPgPbo5Xn/OIoHnKW kf6SZiKBjJTBkSRNIbunpW9KyRzUeWH4aOGeCw47CCNeTh2ZALdRhq9C2vODg95YeGBGFGpJofb Bn5U9wH0k0/Zs4kMFc+m2dV0cfTugJ8Ez3qjZVIsdPWVskaxgkwwZG9+ZHVt3JPUfygMoZTQ046 KzHe2duGgxcU/liVcij8ZR+MWx9AvbhW5NBwiZ8MGcFmpUxhhMZPH6V2Z6pppHz0cPyRmsi/TIs jnngkFCIFNUhWMa4KEbt8LqzChUrRYXvkPaf6df8OJTAmkxK+80l3rTSd85qafyBnEiYLXA22GK pNbaMxsWQdNEF/nKrTLdTbKuTVnT0TXOH8JFlOFf6us79REMH2MpQfbmv6MZW/A9JxXPecegU1p r3iM3zkXObfZkLMVBpPb73FuFX3rygdu9dYlSos11n1xlF62BAqPwl7rW9fv1IRW8/P3SHohzwY w262dgaxmH3wYBMOv8TCR3vO+5+DXvxdysNdb1eWxtcdnJuG/et/CvfmIXkSFQSAV0T97j/aTq6 Fllr7q25aXjPpZ+lDRF5/EkxMRrmdrb91Nobi1uz/EZN8MbpjcsRIs1MVz7OxmrYX4oL86jyLA7 oz3WPxGsBMRNSsg== 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" Implement Ccm algorithm for Mali-C55 base on the libipa CcmAlgorithm class. Mali has configurable colour gains as part of the CCM hardware block. Fix them to 1.0 for the time being as white balance is performed by the dedicated Awb algorithm in the Bayer colour space. The existing Mali Ccm algorithm implementation is a bit more strict on when to re-interpolate the CCM coefficients. Keep the algorithm a little more strict and only interpolate on 20% color temperature changes. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/mali-c55/algorithms/ccm.cpp | 173 ++++++++++++++++++++++++++++++++ src/ipa/mali-c55/algorithms/ccm.h | 66 ++++++++++++ src/ipa/mali-c55/algorithms/meson.build | 1 + src/ipa/mali-c55/ipa_context.h | 3 + src/ipa/mali-c55/params.h | 2 + 5 files changed, 245 insertions(+) diff --git a/src/ipa/mali-c55/algorithms/ccm.cpp b/src/ipa/mali-c55/algorithms/ccm.cpp new file mode 100644 index 000000000000..b5196a7ee85b --- /dev/null +++ b/src/ipa/mali-c55/algorithms/ccm.cpp @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Mali C55 Color Correction Matrix control algorithm + */ + +#include "ccm.h" + +#include + +/** + * \file ccm.h + * \brief Mali-C55 CCM implementation + */ + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +LOG_DEFINE_CATEGORY(MaliC55Ccm) + +/** + * \class Ccm + * \brief Mali-C55 color correction matrix algorithm + */ + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Ccm::init(IPAContext &context, const ValueNode &tuningData) +{ + auto &cmap = context.ctrlMap; + int ret = ccmAlgo_.init(tuningData, cmap); + if (ret) + return ret; + + /* + * Mali-C55 allows to perform WB in the RGB color space as part of the + * CCM. Fix the gains at 1.0 as we perform White Balance in the Bayer + * domain. + */ + gain_ = 1.0; + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Ccm::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + lastCt_ = context.activeState.awb.automatic.colourTemperature; + return ccmAlgo_.configure(context.activeState.ccm, lastCt_); +} + +void Ccm::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + /* Nothing to do here, the ccm will be calculated in prepare() */ + if (frameContext.awb.autoEnabled) + return; + + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); +} + +void Ccm::setParameters(MaliC55Params *params, const IPAFrameContext &frameContext) +{ + auto config = params->block(); + + for (unsigned int i = 0; i < 3; i++) + config->gains[i] = UQ<4, 8>(gain_).quantized(); + + for (unsigned int i = 0; i < 3; i++) + config->offs[i] = frameContext.ccm.offsets[i][0]; + + const Matrix &ccm = frameContext.ccm.ccm; + for (unsigned int i = 0; i < 3; i++) { + for (unsigned int j = 0; j < 3; j++) { + uint16_t val = Q<5, 8>(ccm[i][j]).quantized(); + + /* + * CCM coefficients are expected to be in sign/magnitude + * format. + * + * As we're here handling the case where ccm[i][j] is + * negative, its quantized representation is stored in + * 'val' as 2's complement. + * + * We need to invert the 2's complement by + * re-complementing it to 1 and adding 1 back. + * + * Let's make a practical example on how to reverse the + * 2's complement of -7 with 4 bits and BIT(5) for sign. + * + * 2's complement (ignore sign bit) + * - 1's complement of abs(-7) + * 2^4 - 1 - 7 = 8 -> 1000 + * - Add one: 8 + 1 = 9 -> 1001 + * + * -7 is then [1 1001] in memory in 2's complement + * + * Reverse 2's complement (ignore sign bit) + * - 1's complement of the 2's complement representation + * 2^4 - 1 - 9 = 6 -> 0110 + * - add one: 6 + 1 = 7 -> 0111 + * + * -7 is then [1 0111] in memory in Sign/Magnitude + */ + if (val && ccm[i][j] < 0) + val = ((~(val & ~(1 << 12)) & 0x1fff) + 1) | (1 << 12); + + config->coeffs[i][j] = val; + } + } + + config.setEnabled(true); + + LOG(MaliC55Ccm, Debug) << "Setting ccm: " << ccm << " offsets: " + << frameContext.ccm.offsets + << " with fixed gain " << gain_; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Ccm::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, MaliC55Params *params) +{ + if (!frameContext.awb.autoEnabled) { + setParameters(params, frameContext); + return; + } + + /* + * The Mali-C55 Ccm implementation is slightly stricter than the + * CcmAlgorithm class and only re-interpolates if the colour temperature + * changes of a certain amount. + */ + float ct = frameContext.awb.colourTemperature * 1.0f; + if (frame > 0 && (ct < lastCt_ * 1.2 && ct > lastCt_ * 0.8)) { + frameContext.ccm.ccm = context.activeState.ccm.automatic.ccm; + frameContext.ccm.offsets = context.activeState.ccm.automatic.offsets; + lastCt_ = ct; + + return; + } + + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame, ct); + setParameters(params, frameContext); + lastCt_ = ct; +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Ccm::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const mali_c55_stats_buffer *stats, + ControlList &metadata) +{ + ccmAlgo_.process(frameContext.ccm, metadata); +} + +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/ccm.h b/src/ipa/mali-c55/algorithms/ccm.h new file mode 100644 index 000000000000..73649204a7ee --- /dev/null +++ b/src/ipa/mali-c55/algorithms/ccm.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Mali C55 Color Correction Matrix control algorithm + */ + +#pragma once + +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/ccm.h" +#include "libipa/fixedpoint.h" + +#include "algorithm.h" +#include "ipa_context.h" +#include "params.h" + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +class Ccm : public Algorithm +{ +public: + Ccm() {} + ~Ccm() = default; + + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + MaliC55Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + ControlList &metadata) override; + +private: + void setParameters(MaliC55Params *params, const IPAFrameContext &context); + + /* + * The CCM coefficient registers are said to be in Q<4,8> but this + * doesn't include the sign bit as the register is 13 bits wide + * (Q-format TI variant). + * + * As the Quantized class uses the ARM variant of the Q-format notation, + * make it <5, 8> to include the sign bit. + */ + CcmAlgorithm> ccmAlgo_; + float gain_; + float lastCt_; +}; + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build index 1665da071634..24a27ce66562 100644 --- a/src/ipa/mali-c55/algorithms/meson.build +++ b/src/ipa/mali-c55/algorithms/meson.build @@ -4,5 +4,6 @@ mali_c55_ipa_algorithms = files([ 'agc.cpp', 'awb.cpp', 'blc.cpp', + 'ccm.cpp', 'lsc.cpp', ]) diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index 7dc765a3542b..ef3188e347d7 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -15,6 +15,7 @@ #include #include "libipa/awb.h" +#include "libipa/ccm.h" #include "libipa/fixedpoint.h" namespace libcamera { @@ -56,6 +57,7 @@ struct IPAActiveState { } agc; ipa::awb::ActiveState awb; + ipa::ccm::ActiveState ccm; }; struct IPAFrameContext : public FrameContext { @@ -66,6 +68,7 @@ struct IPAFrameContext : public FrameContext { } agc; ipa::awb::FrameContext awb; + ipa::ccm::FrameContext ccm; }; struct IPAContext { diff --git a/src/ipa/mali-c55/params.h b/src/ipa/mali-c55/params.h index 3abcb7f94916..2ce55262031d 100644 --- a/src/ipa/mali-c55/params.h +++ b/src/ipa/mali-c55/params.h @@ -29,6 +29,7 @@ enum class MaliC55Blocks : uint16_t { AwbConfig, MeshShadingConfig, MeshShadingSel, + Ccm, }; namespace details { @@ -55,6 +56,7 @@ MALI_C55_DEFINE_BLOCK_TYPE(AwbGains, awb_gains, BLOCK_AWB_GAINS); MALI_C55_DEFINE_BLOCK_TYPE(AwbConfig, awb_config, BLOCK_AWB_CONFIG); MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingConfig, mesh_shading_config, MESH_SHADING_CONFIG); MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingSel, mesh_shading_selection, MESH_SHADING_SELECTION); +MALI_C55_DEFINE_BLOCK_TYPE(Ccm, ccm, BLOCK_CCM); struct param_traits { using id_type = MaliC55Blocks; From patchwork Wed Aug 5 16:12:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27632 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 65F9AC333C for ; Wed, 5 Aug 2026 16:13:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 488C368160; Wed, 5 Aug 2026 18:13:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="twebsNro"; 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 94C1268137 for ; Wed, 5 Aug 2026 18:13:30 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BE751981; Wed, 5 Aug 2026 18:12:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946340; bh=r5RK5eMNhW6KS9saJ6/fZWiL7ukwpBrJrnd7dbUPYDg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=twebsNroPyacb3qAxKV5UgfaTkCru3YTnrwxBap7XHRBkoHex5MvVtA6mVwcBskIf odSJFYPYUTgEmIKFXNlgvpFETKhInPPhGolI8apdlfBgbBjinrID5e4aVc89HX35NZ HsG39Ekx9X8THC1hrvmD2tU1PKF0DAoF3ip30CyQ= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:51 +0200 Subject: [PATCH v7 09/32] ipa: rkisp1: lsc: Re-architecture Lsc support MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-9-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=15654; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=r5RK5eMNhW6KS9saJ6/fZWiL7ukwpBrJrnd7dbUPYDg=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Eb782upxkwhnnl70UjoHfoSJtL/7UmDClAC BAvoKZ9JzmJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhGwAKCRByNAaPFqFW PNgeEACAj/a6BnaQFJNjDFr82RheGHfCdCSfceMSgFZ+H1+PjpB3L5uIRfSlqUSuOAU+JVcgPjU Wmfq2Ev6/0ECNllE1ROFEJeUSn7sFmOZOU/GBYP0yaKRm9hTdng24xYAHAtAuw1RBWQRQM/QGb9 iMabkWCat0DQXsIooxl046POsFSLyh8gOiSHXOOtck4er9gXq2U1w6iDDlMRbXEIS1UH9nXClFJ bBcsEtEt/Nj0Adet+n+UHellB2AAeLuH+/QqRWmDaBCzoG+2J3qAOkCAq118/+4zNVabjW0SDiO /IQ4Ic/zXCAI30Ty3xoFu4R2TzL1OkXi4oaaMXG3iO0+3kCt+IzVO+5+8iyudsXuFEcreK4mlDY OeeIXgvYesC02wAM9hO67z9AZz/qaQWipQdSvyMfMXayBEKScRy8mjA2nI8Wk4OwDLTAMN/DJo3 HBbaiXpS2J8NkpkKfh8/wxtvXgsHzleFob1b5BEXeYFTftfWMD+RFneT/48gXlJj9tKU2WDC1N8 5sCljVqsEacNd3KLQvZLjarQFcmIJv2PKE9b9Fa8uFRXWeIIa/gyuhjUjQL7ZHrYXpTzViweQWn DkJdwzHbHtzjnHpYYh/WNKK5elHNXNvZ2QpCJOAVVL97s1hpKvfQwzhm4pwyY6lxVbtMi9stxW/ eU3zpazg0KJJhrw== 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" The RkISP1 LSC implementation is realized by the following components: - 2 "tuning file" parser classes: - LscPolynomialLoader - LscTableLoader which implement the "parseLscData" function that loads from tuning file a polynomial representation of the lsc gains or a table of gains respectively. - A hierachy of "ShadingDescriptors" - LscPolynomialShadingDescriptor - LscTableShadingDescriptor that implement the "sampleForCrop" interface which expands a polynomial to a list of gains for the LscPolynomialShadingDescriptor and simply return the data parsed from tuning file for LscTableShadingDescriptor. Unify the two functionalities in a single class hierarchy whose interface is defined by the LscImplementation pure virtual class implemented by LscPolynomialImpl and LscTableImpl. The LensShadingCorrection algorithm class instantiates the correct sub-class and uses the file parsing and table expansion functionalities by implementing the composition pattern. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/rkisp1/algorithms/lsc.cpp | 247 +++++++++++++++++--------------------- src/ipa/rkisp1/algorithms/lsc.h | 49 ++++---- 2 files changed, 141 insertions(+), 155 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 06f5913c5750..38380aa839c6 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -54,17 +54,6 @@ void Interpolator:: namespace ipa::rkisp1::algorithms { -/** - * \class LensShadingCorrection - * \brief RkISP1 Lens Shading Correction control - * - * Due to the optical characteristics of the lens, the light intensity received - * by the sensor is not uniform. - * - * The Lens Shading Correction algorithm applies multipliers to all pixels to - * compensate for the lens shading effect. The coefficients are specified in a - * downscaled table in the tuning data. - */ LOG_DEFINE_CATEGORY(RkISP1Lsc) @@ -72,54 +61,110 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -class LscPolynomialShadingDescriptor : public LensShadingCorrection::ShadingDescriptor +class LscPolynomialImpl : public LscImplementation { +private: + struct PolynomialComponents { + LscPolynomial pr; + LscPolynomial pgr; + LscPolynomial pgb; + LscPolynomial pb; + }; + using PolynomialComponentsMap = std::map; + public: - LscPolynomialShadingDescriptor(const LscPolynomial &pr, const LscPolynomial &pgr, - const LscPolynomial &pgb, const LscPolynomial &pb) - : pr_(pr), pgr_(pgr), pgb_(pgb), pb_(pb) + LscPolynomialImpl(const Size &sensorSize) + : sensorSize_(sensorSize) { } - LensShadingCorrection::Components sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) override; + int parseLscData(const ValueNode &sets) override; + + LensShadingCorrection::ComponentsMap + sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) override; private: + std::vector sizesListToPositions(Span sizes); std::vector samplePolynomial(const LscPolynomial &poly, Span xPositions, Span yPositions, const Rectangle &cropRectangle); + PolynomialComponentsMap lscData_; + Size sensorSize_; +}; - std::vector sizesListToPositions(Span sizes); +int LscPolynomialImpl::parseLscData(const ValueNode &sets) +{ + for (const auto &set : sets.asList()) { + std::optional pr, pgr, pgb, pb; + uint32_t ct = set["ct"].get(0); - LscPolynomial pr_; - LscPolynomial pgr_; - LscPolynomial pgb_; - LscPolynomial pb_; -}; + if (lscData_.count(ct)) { + LOG(RkISP1Lsc, Error) + << "Multiple sets found for " + << "color temperature " << ct; + return -EINVAL; + } + + pr = set["r"].get(); + pgr = set["gr"].get(); + pgb = set["gb"].get(); + pb = set["b"].get(); + + if (!(pr || pgr || pgb || pb)) { + LOG(RkISP1Lsc, Error) + << "Failed to parse polynomial for " + << "colour temperature " << ct; + return -EINVAL; + } -LensShadingCorrection::Components -LscPolynomialShadingDescriptor::sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) + pr->setReferenceImageSize(sensorSize_); + pgr->setReferenceImageSize(sensorSize_); + pgb->setReferenceImageSize(sensorSize_); + pb->setReferenceImageSize(sensorSize_); + + lscData_.emplace(std::piecewise_construct, + std::forward_as_tuple(ct), + std::forward_as_tuple(PolynomialComponents{ *pr, *pgr, *pgb, *pb })); + } + + if (lscData_.empty()) { + LOG(RkISP1Lsc, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; +} + +LensShadingCorrection::ComponentsMap +LscPolynomialImpl::sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) { std::vector xPos = sizesListToPositions(xSizes); std::vector yPos = sizesListToPositions(ySizes); - return { - .r = samplePolynomial(pr_, xPos, yPos, cropRectangle), - .gr = samplePolynomial(pgr_, xPos, yPos, cropRectangle), - .gb = samplePolynomial(pgb_, xPos, yPos, cropRectangle), - .b = samplePolynomial(pb_, xPos, yPos, cropRectangle) - }; + LensShadingCorrection::ComponentsMap components; + + for (const auto &[k, p] : lscData_) { + components[k] = { + samplePolynomial(p.pr, xPos, yPos, cropRectangle), + samplePolynomial(p.pgr, xPos, yPos, cropRectangle), + samplePolynomial(p.pgb, xPos, yPos, cropRectangle), + samplePolynomial(p.pb, xPos, yPos, cropRectangle) + }; + } + + return components; } std::vector -LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle) +LscPolynomialImpl::samplePolynomial(const LscPolynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle) { double m = poly.getM(); double x0 = cropRectangle.x / m; @@ -160,7 +205,7 @@ LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &poly, * output: 0.0 0.2 0.5 0.8 1.0 */ std::vector -LscPolynomialShadingDescriptor::sizesListToPositions(Span sizes) +LscPolynomialImpl::sizesListToPositions(Span sizes) { const int half = sizes.size(); std::vector positions(half * 2 + 1); @@ -176,74 +221,12 @@ LscPolynomialShadingDescriptor::sizesListToPositions(Span sizes) return positions; } -class LscPolynomialLoader +class LscTableImpl : public LscImplementation { public: - LscPolynomialLoader(const Size &sensorSize) - : sensorSize_(sensorSize) - { - } - - int parseLscData(const ValueNode &sets, - LensShadingCorrection::ShadingDescriptorMap &lscData); + int parseLscData(const ValueNode &sets) override; -private: - Size sensorSize_; -}; - -int LscPolynomialLoader::parseLscData(const ValueNode &sets, - LensShadingCorrection::ShadingDescriptorMap &lscData) -{ - for (const auto &set : sets.asList()) { - std::optional pr, pgr, pgb, pb; - uint32_t ct = set["ct"].get(0); - - if (lscData.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for " - << "color temperature " << ct; - return -EINVAL; - } - - pr = set["r"].get(); - pgr = set["gr"].get(); - pgb = set["gb"].get(); - pb = set["b"].get(); - - if (!(pr || pgr || pgb || pb)) { - LOG(RkISP1Lsc, Error) - << "Failed to parse polynomial for " - << "colour temperature " << ct; - return -EINVAL; - } - - pr->setReferenceImageSize(sensorSize_); - pgr->setReferenceImageSize(sensorSize_); - pgb->setReferenceImageSize(sensorSize_); - pb->setReferenceImageSize(sensorSize_); - - lscData.emplace( - ct, std::make_unique( - *pr, *pgr, *pgb, *pb)); - } - - if (lscData.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; -} - -class LscTableShadingDescriptor : public LensShadingCorrection::ShadingDescriptor -{ -public: - LscTableShadingDescriptor(LensShadingCorrection::Components components) - : lscData_(std::move(components)) - { - } - - LensShadingCorrection::Components + LensShadingCorrection::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, [[maybe_unused]] Span xSizes, [[maybe_unused]] Span ySizes) override @@ -253,28 +236,19 @@ public: return lscData_; } -private: - LensShadingCorrection::Components lscData_; -}; - -class LscTableLoader -{ -public: - int parseLscData(const ValueNode &sets, - LensShadingCorrection::ShadingDescriptorMap &lscData); - private: std::vector parseTable(const ValueNode &tuningData, const char *prop); + + LensShadingCorrection::ComponentsMap lscData_; }; -int LscTableLoader::parseLscData(const ValueNode &sets, - LensShadingCorrection::ShadingDescriptorMap &lscData) +int LscTableImpl::parseLscData(const ValueNode &sets) { for (const auto &set : sets.asList()) { uint32_t ct = set["ct"].get(0); - if (lscData.count(ct)) { + if (lscData_.count(ct)) { LOG(RkISP1Lsc, Error) << "Multiple sets found for color temperature " << ct; @@ -295,11 +269,10 @@ int LscTableLoader::parseLscData(const ValueNode &sets, return -EINVAL; } - lscData.emplace( - ct, std::make_unique(std::move(components))); + lscData_.emplace(ct, std::move(components)); } - if (lscData.empty()) { + if (lscData_.empty()) { LOG(RkISP1Lsc, Error) << "Failed to load any sets"; return -EINVAL; } @@ -307,7 +280,7 @@ int LscTableLoader::parseLscData(const ValueNode &sets, return 0; } -std::vector LscTableLoader::parseTable(const ValueNode &tuningData, +std::vector LscTableImpl::parseTable(const ValueNode &tuningData, const char *prop) { static constexpr unsigned int kLscNumSamples = @@ -363,6 +336,18 @@ unsigned int quantize(unsigned int value, unsigned int step) } /* namespace */ +/** + * \class LensShadingCorrection + * \brief RkISP1 Lens Shading Correction control + * + * Due to the optical characteristics of the lens, the light intensity received + * by the sensor is not uniform. + * + * The Lens Shading Correction algorithm applies multipliers to all pixels to + * compensate for the lens shading effect. The coefficients are specified in a + * downscaled table in the tuning data. + */ + LensShadingCorrection::LensShadingCorrection() : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) { @@ -388,22 +373,21 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, return -EINVAL; } - ShadingDescriptorMap lscData; - int ret = 0; + int ret; std::string type = tuningData["type"].get("table"); if (type == "table") { LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data."; - auto loader = LscTableLoader(); - ret = loader.parseLscData(sets, lscData); + algo_ = std::make_unique(); + ret = algo_->parseLscData(sets); } else if (type == "polynomial") { LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data."; /* * \todo: Most likely the reference frame should be native_size. * Let's wait how the internal discussions progress. */ - auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize); - ret = loader.parseLscData(sets, lscData); + algo_ = std::make_unique(context.sensorInfo.activeAreaSize); + ret = algo_->parseLscData(sets); } else { LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" << type << "'"; @@ -416,8 +400,6 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, context.ctrlMap[&controls::LensShadingCorrectionEnable] = ControlInfo(false, true, true); - shadingDescriptors_ = std::move(lscData); - return 0; } @@ -453,11 +435,8 @@ int LensShadingCorrection::configure(IPAContext &context, } LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop; - std::map shadingData; - for (const auto &[t, descriptor] : shadingDescriptors_) - shadingData[t] = descriptor->sampleForCrop(configInfo.analogCrop, - xSize_, ySize_); - + ComponentsMap shadingData = algo_->sampleForCrop(configInfo.analogCrop, + xSize_, ySize_); sets_.setData(std::move(shadingData)); context.activeState.lsc.enabled = true; diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 0a256e225327..3463531f2ab2 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -18,9 +18,20 @@ namespace libcamera { namespace ipa::rkisp1::algorithms { +class LscImplementation; + class LensShadingCorrection : public Algorithm { public: + struct Components { + std::vector r; + std::vector gr; + std::vector gb; + std::vector b; + }; + + using ComponentsMap = std::map; + LensShadingCorrection(); ~LensShadingCorrection() = default; @@ -36,31 +47,10 @@ public: IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, ControlList &metadata) override; - - struct Components { - std::vector r; - std::vector gr; - std::vector gb; - std::vector b; - }; - - class ShadingDescriptor - { - public: - virtual ~ShadingDescriptor() = default; - virtual Components sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) = 0; - }; - - using ShadingDescriptorMap = std::map>; - private: void setParameters(rkisp1_cif_isp_lsc_config &config); void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0); - ShadingDescriptorMap shadingDescriptors_; - ipa::Interpolator sets_; std::vector xSize_; std::vector ySize_; uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; @@ -70,6 +60,23 @@ private: unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; + + ipa::Interpolator sets_; + + std::unique_ptr algo_; +}; + +class LscImplementation +{ +public: + virtual ~LscImplementation() {} + + virtual int parseLscData(const ValueNode &sets) = 0; + + virtual LensShadingCorrection::ComponentsMap + sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) = 0; }; } /* namespace ipa::rkisp1::algorithms */ From patchwork Wed Aug 5 16:12:52 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27633 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 756CEC333D for ; Wed, 5 Aug 2026 16:13:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CDD5068140; Wed, 5 Aug 2026 18:13:39 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Fg/6HhpC"; 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 3B9F568141 for ; Wed, 5 Aug 2026 18:13:31 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6691C399E; Wed, 5 Aug 2026 18:12:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946340; bh=p5n4yaipXwbGtH6iHWbs5GerOJZpwjbVycsFih2ycp8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Fg/6HhpCNRYXcc+3UXpnK38MlnR6wBBbT93tkhJwooV6kNiQ0zN0wfjw7WSkDxay0 Z0wFpBslJc5oyLGS7GsANE24ViJd1FUgbdRhIoMEI6i1DyRNLPqG+0LZ1m+UNeVcmy HWgOxUdaGT1sDN9yJG3kXRVOS10ImQMwnXzSJ8N4= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:52 +0200 Subject: [PATCH v7 10/32] ipa: libipa: Introduce lsc_base.h MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-10-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=11286; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=p5n4yaipXwbGtH6iHWbs5GerOJZpwjbVycsFih2ycp8=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EcRiJ2/AvlPob74MSygIHkdI5gA3m+YyGeV XcHBmMddmyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhHAAKCRByNAaPFqFW PP/pEACyEUj2q2ijmJx2Ti9Fu0SNFACkK2xLYj1nEcdLiEioaYXJ89DingbEOenAAeOoWDl4Hkn mD6nI9gXTqCpl/scp8OUyZREvhkUJvCVKR7V3fqgOC/lyFyVb5Ph2Og/TvyC2UuM5l9nO4SZZMZ 1BsRVSULVZ/o73w2XnyEJrEo+1T5jsLOAdpsfyRp7PI5W11XfVWZe04Vh8BMACDGK7V4BJWyRmu 7wDA1G37jr2fGoinmGFmoxRFQHfTx/SS8CPST+J0MsuXh6XlT2/8fJCp/tvSXVcfzz2W5MJmIV+ H1+38FuiOJTXXGbeurneAXvcZDMYJGc9IXrItRJjLio2lGofUcX2rD07159VyZr79peYwAj3PJA 0InrARLN7XVYNCUaymDoj0V0/yI2QzbFypQbGecKLtAGNb2YYkhiJEeFwCHuo3cbkP4408nVZ47 StaoHvgTMgFhvXkaxt30MdfUpSa+p/3va+s2b9RfoFQaA33b8a2KO+ni2GvXuVCMScxRvfravCc Yt2jJ36NvYjLu67B2NOqLXsCKiSnrYY17TAwlr69thokR6OaMpJMASrjOZGnV7gu/gSp7b5u3Ec pKmyNVVIFdkMwx+Ja3SQSJc+5JHiuBsqxoQAWeqtO0e6Y537Buu/l8HVUNj5cK5f/MKkyeeY/KC ewZZTZKCVKySB7w== 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" Provide a header and implementation for the base definitions of common lens shading calibration data types, and move re-usable code out from the RkISP1 lsc algorithm. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_base.cpp | 95 +++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc_base.h | 53 ++++++++++++++++++++++ src/ipa/libipa/meson.build | 2 + src/ipa/rkisp1/algorithms/lsc.cpp | 28 ++++++------ src/ipa/rkisp1/algorithms/lsc.h | 29 ++---------- 5 files changed, 167 insertions(+), 40 deletions(-) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp new file mode 100644 index 000000000000..26197f4effe5 --- /dev/null +++ b/src/ipa/libipa/lsc_base.cpp @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * Base classes and types for LSC algorithm implementations + */ + +#include "lsc_base.h" + +/** + * \file lsc_base.h + * \brief Base types and definitions for LscImplementation class hierarchy + */ + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +/** + * \struct Components + * \brief Associate colour components with a list of gains + * + * LSC tables are defined as a list of gain values associated to a colour + * component. + * + * \var Components::r + * \brief The list of gains for the Red colour component + * + * \var Components::gr + * \brief The list of gains for the Green/Red colour component + * + * \var Components::gb + * \brief The list of gains for the Green/Blue colour component + * + * \var Components::b + * \brief The list of gains for the Blue colour component + */ + +/** + * \typedef ComponentsMap + * \brief Associate a colour temperature to a LSC table + * + * An LSC table is generated during the tuning phase for a specific colour + * temperature, and a tuning file usually contains LSC tables generated for + * several different colour temperatures. + */ + +} /* namespace lsc */ + +/** + * \class LscImplementation + * \brief Pure virtual base class for LSC algorithm implementations + * + * Defines the interface for the LSC algorithm implementation. + */ + +/** + * \fn LscImplementation::~LscImplementation + * \brief Virtual class destructor + */ + +/** + * \fn LscImplementation::parseLscData + * \brief Parse \a tuningData + * \param[in] tuningData The tuning data + * + * \return 0 on success, a negative error number otherwise + */ + +/** + * \fn LscImplementation::sampleForCrop + * \brief Re-sample the LSC components for \a cropRectangle + * \param[in] cropRectangle The sensor analogue crop rectangle + * \param[in] xSizes List of horizontal positions of the LSC grid nodes + * \param[in] ySizes List of vertical positions of the LSC grid nodes + * + * LSC tables have to be re-sampled every time a new sensor configuration is + * used, as each streaming session might use a different sensor crop rectangle + * \a cropRectangle. + * + * \a cropRectangle represents the size of the frame on which the LSC tables + * have to be re-sampled on. + * + * \a xSizes and \a ySizes represent the position of the grid nodes vertexes in + * the [0, 1] interval. For example an equally spaced grid of 16 nodes will have + * each segment of size 0.0625 and the list of nodes position will be + * [0, 0.0625, 0.125, 0.1875, ... , 1]. It is expected that the first position + * is 0 and the last position is 1. + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h new file mode 100644 index 000000000000..59b831e53fbb --- /dev/null +++ b/src/ipa/libipa/lsc_base.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * Base classes and types for LSC algorithm implementations + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include + +#include "libcamera/internal/value_node.h" + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +struct Components { + std::vector r; + std::vector gr; + std::vector gb; + std::vector b; +}; + +using ComponentsMap = std::map; + +} /* namespace lsc */ + +class LscImplementation +{ +public: + virtual ~LscImplementation() {} + + virtual int parseLscData(const ValueNode &tuningData) = 0; + + virtual lsc::ComponentsMap + sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) = 0; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 635abad778e6..5682212ce9f6 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -14,6 +14,7 @@ libipa_headers = files([ 'fixedpoint.h', 'histogram.h', 'interpolator.h', + 'lsc_base.h', 'lsc_polynomial.h', 'lux.h', 'module.h', @@ -37,6 +38,7 @@ libipa_sources = files([ 'fixedpoint.cpp', 'histogram.cpp', 'interpolator.cpp', + 'lsc_base.cpp', 'lsc_polynomial.cpp', 'lux.cpp', 'module.cpp', diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 38380aa839c6..af919f7429af 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -38,10 +38,10 @@ void interpolateVector(const std::vector &a, const std::vector &b, } template<> -void Interpolator:: - interpolate(const rkisp1::algorithms::LensShadingCorrection::Components &a, - const rkisp1::algorithms::LensShadingCorrection::Components &b, - rkisp1::algorithms::LensShadingCorrection::Components &dest, +void Interpolator:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, double lambda) { interpolateVector(a.r, b.r, dest.r, lambda); @@ -80,7 +80,7 @@ public: int parseLscData(const ValueNode &sets) override; - LensShadingCorrection::ComponentsMap + lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, Span xSizes, Span ySizes) override; @@ -138,7 +138,7 @@ int LscPolynomialImpl::parseLscData(const ValueNode &sets) return 0; } -LensShadingCorrection::ComponentsMap +lsc::ComponentsMap LscPolynomialImpl::sampleForCrop(const Rectangle &cropRectangle, Span xSizes, Span ySizes) @@ -146,7 +146,7 @@ LscPolynomialImpl::sampleForCrop(const Rectangle &cropRectangle, std::vector xPos = sizesListToPositions(xSizes); std::vector yPos = sizesListToPositions(ySizes); - LensShadingCorrection::ComponentsMap components; + lsc::ComponentsMap components; for (const auto &[k, p] : lscData_) { components[k] = { @@ -226,7 +226,7 @@ class LscTableImpl : public LscImplementation public: int parseLscData(const ValueNode &sets) override; - LensShadingCorrection::ComponentsMap + lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, [[maybe_unused]] Span xSizes, [[maybe_unused]] Span ySizes) override @@ -240,7 +240,7 @@ private: std::vector parseTable(const ValueNode &tuningData, const char *prop); - LensShadingCorrection::ComponentsMap lscData_; + lsc::ComponentsMap lscData_; }; int LscTableImpl::parseLscData(const ValueNode &sets) @@ -255,7 +255,7 @@ int LscTableImpl::parseLscData(const ValueNode &sets) return -EINVAL; } - LensShadingCorrection::Components components; + lsc::Components components; components.r = parseTable(set, "r"); components.gr = parseTable(set, "gr"); components.gb = parseTable(set, "gb"); @@ -435,8 +435,8 @@ int LensShadingCorrection::configure(IPAContext &context, } LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop; - ComponentsMap shadingData = algo_->sampleForCrop(configInfo.analogCrop, - xSize_, ySize_); + lsc::ComponentsMap shadingData = algo_->sampleForCrop(configInfo.analogCrop, + xSize_, ySize_); sets_.setData(std::move(shadingData)); context.activeState.lsc.enabled = true; @@ -452,7 +452,7 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) } void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, - const Components &set) + const lsc::Components &set) { std::copy(set.r.begin(), set.r.end(), &config.r_data_tbl[0][0]); std::copy(set.gr.begin(), set.gr.end(), &config.gr_data_tbl[0][0]); @@ -518,7 +518,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, setParameters(*config); - const Components &set = sets_.getInterpolated(quantizedCt); + const lsc::Components &set = sets_.getInterpolated(quantizedCt); copyTable(*config, set); lastAppliedCt_ = ct; diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 3463531f2ab2..b21c91c0d81f 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -11,6 +11,7 @@ #include #include "libipa/interpolator.h" +#include "libipa/lsc_base.h" #include "algorithm.h" @@ -18,20 +19,9 @@ namespace libcamera { namespace ipa::rkisp1::algorithms { -class LscImplementation; - class LensShadingCorrection : public Algorithm { public: - struct Components { - std::vector r; - std::vector gr; - std::vector gb; - std::vector b; - }; - - using ComponentsMap = std::map; - LensShadingCorrection(); ~LensShadingCorrection() = default; @@ -49,7 +39,7 @@ public: ControlList &metadata) override; private: void setParameters(rkisp1_cif_isp_lsc_config &config); - void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0); + void copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set0); std::vector xSize_; std::vector ySize_; @@ -61,23 +51,10 @@ private: unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; - ipa::Interpolator sets_; + ipa::Interpolator sets_; std::unique_ptr algo_; }; -class LscImplementation -{ -public: - virtual ~LscImplementation() {} - - virtual int parseLscData(const ValueNode &sets) = 0; - - virtual LensShadingCorrection::ComponentsMap - sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) = 0; -}; - } /* namespace ipa::rkisp1::algorithms */ } /* namespace libcamera */ From patchwork Wed Aug 5 16:12:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27634 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 77341C330F for ; Wed, 5 Aug 2026 16:13:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D1DA06816C; Wed, 5 Aug 2026 18:13:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UlOaQ1oY"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B9AF968140 for ; Wed, 5 Aug 2026 18:13:31 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0046839A0; Wed, 5 Aug 2026 18:12:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946341; bh=iym+xPwKbdXJhKWKBEgCczMlRqIVu/lwQ9sWa5xwMiM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=UlOaQ1oYWazbCrizmAoUiSXBaSwjJJsPRfZTQhPUAzBeoR99bIhTaUdEIqUXTEPQz XcWQ/iEpfgk2nV84NXJtTRuJnxs8sWQHZ+RPr4ZCNe4+kxytpJirCwLiuBC3L7Er3i +baEPvFWdAwx+FpsDBC8bV41JeAVDcPPjWgvXxWQ= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:53 +0200 Subject: [PATCH v7 11/32] ipa: libipa: lsc_polynomial: Fix polynomial parsing error MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-11-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=1449; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=iym+xPwKbdXJhKWKBEgCczMlRqIVu/lwQ9sWa5xwMiM=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EcIX3th44+oDmMbMzn72qRttNqiI8Jw/LmC aCDgG+BgtmJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhHAAKCRByNAaPFqFW PGzoD/4v9eH91AgjtePUpdshgWKRygK/q/zV2QIeADuN3GJK/0lS3xHqZFRwfuDOzROLez9FRKp VGrd162m1Pui/f1VNASBGux+4e2CI8eKmHTk6kS5WFp9kNlja4SOWONn6R81QVGsn5ghVxW+NhZ bMycYcLiIQCe4edrCc/6VITMDeu1NwP4aX5Xuq5+x045BnZOlyl0+CWW+to0JQCJ65UTSjj2tni ui570HwfvxLs/Qvjw49aYcbVAs5W/Iixq5jivEEa7e0X7xZtPpQ6RpFbmhKCGN5+Wa03WZYFw6a +A/4FxkxXjxLTFN4Bq1pTaTlo6EmhwhTHnVAv0nnTukFGO8GfI3ZuwSr7t7Oeiux2H8WQW8M1MQ GzE/AndDHipM3c0reUkRNqcPAEAK45HP77JAY5uFlnZw4p9qTUC8wdCDL3UrEIvIygYPQV901TA 9Vlrelv8CZuW/G8qNDaXdLUmMecegKmqMVQSxMa8c4uiImr2ck9smXtuqs5V3h7ZvQLuni8EurG dO1IuW2tt5Yp8BShaBFIKr/CSL2camkdGN2PCNe7hPne1qpn698/f5luUjpPXud7JzO5QqcX3bx 4ocDGAM1BrJ6TR+JaLYKEJyjCfNi1GWnfk0KHWh4MCWfjeAaeNQPtvh3fLPuqi3xvlGFND4YbDc 9tEqMkVpY7l51aw== 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" If any of the polynomial component is missing in the tuning file the ValueNode::Accessor specialization emits an error message but continues and dereferences a potentially invalid object. Fix it by returning std::nullopt if a polynomial component is missing. The caller already checks if the returned value is valid or not, causing the algorithm initialization to fail if the tuning file is invalid. Suggested-by: Milan Zamazal Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_polynomial.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index d7d9ae42e360..a0d27c4b21d6 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -94,9 +94,11 @@ struct ValueNode::Accessor { std::optional k3 = obj["k3"].get(); std::optional k4 = obj["k4"].get(); - if (!(cx && cy && k0 && k1 && k2 && k3 && k4)) + if (!(cx && cy && k0 && k1 && k2 && k3 && k4)) { LOG(LscPolynomial, Error) << "Polynomial is missing a parameter"; + return std::nullopt; + } return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); } From patchwork Wed Aug 5 16:12:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27635 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 A58C8C333E for ; Wed, 5 Aug 2026 16:13:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E149B68160; Wed, 5 Aug 2026 18:13:43 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vsIIP8aT"; 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 6D86268127 for ; Wed, 5 Aug 2026 18:13:32 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 88AA01387; Wed, 5 Aug 2026 18:12:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946342; bh=yXwIPDDYuWnxiEQ7pfsAy2nzJEmWvfcuWcpTAAq3aio=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=vsIIP8aTsAqkI8S30j1LPgZHZ8rZTxjRKIlIPcCdalDmDunbk/sWU1ICaw4Hah1gY aIaz7Jxeuea2h74zVevfpCRWevjFK/2WmB7DtpinzW1ZNQjEud9enXIiHGy7aItDgt Jp6DZ9NZEcAFUBdJ8pNmoLshy2dBafsiAARz0/1c= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:54 +0200 Subject: [PATCH v7 12/32] ipa: libipa: lsc_polynomial: Don't inline ValueNode accessor MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-12-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Laurent Pinchart , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=3022; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=gKG7oMvPdTnWPY3Xqu6aZDyF064c3RNYURQAJyWHzUw=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Ecn6sfB4NARzz6SZlyx0IjbKcD+EOPeMzv3 ec016jRJpiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhHAAKCRByNAaPFqFW PNvVEACORUAzF7U/J1YSBW7iafYJ68uyc6wAv/c4vIDmAwl1xyffPqlH6kAdS5Xuyvb7q+/6NbO wZpMao9WpYGN/90hROLEcaDheUNjYbbcyuY8n47FDByKDWcr7Q5fnl0auaiAe5qPoftjJwyjnZ/ 3KlpPtsfXhF8iQp0lMyTaxxHZm3/+E/RLs213ZtaWhSTN91/QBc7UaHjwNv2AUkeGmtVv0rWUTC Wn7uddWkdSUmi/AxfdAvzbvIXh6gRA7G2XObievL3jt1V01w8WHJthzILv82WR8XpWwqGsOlSGf g8JYWmcoOW+ltsVz6KV2Y3hwjcmhaNYMvRnoXmuD2wlQ3AyJ3kxfVHjxsC9jPDrJlhqVISZiwNb yQ+D92Q0VYdzEBYnuLhMwaYHZJ3CFhRzObtXBd+jeVWnUwtB91GqFuLWBixg1Qjd0j7M7QR/7gF /+iqM5C108aQgxrh1rdayQlWU2XfXEwXcBkZUz7OhWPKhQVsXNH9ORaIPBdlUcClZGQbH3aGbIL BfbN5H+uANni0pVjCYrbvxhZItHWQ0mshdrCnLxaTR62AwQJp8p5+/kZYSWhJU8QId9nsKLn/CX YUsno3gYADeKvF7u01PcTaPHJLsMQe02N0CqcMHbBWvPjWccn2pYcOiDjFDWf6NzN6XcE6FTTOm GR95tKSkTOrtVYg== 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" From: Laurent Pinchart There's no need to inline the ValueNode::Accessor::get() function for LscPolynomial. Move it to from lsc_polynomial.h to lsc_polynomial.cpp. While at it, replace C++-style comments with C-style comments according to the libcamera coding style. Signed-off-by: Laurent Pinchart Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc_polynomial.cpp | 28 ++++++++++++++++++++++++++-- src/ipa/libipa/lsc_polynomial.h | 26 -------------------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index f607d86c54c3..921af0b0bd13 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -77,5 +77,29 @@ namespace ipa { * \param size The size of the reference image */ -} // namespace ipa -} // namespace libcamera +} /* namespace ipa */ + +#ifndef __DOXYGEN__ +template<> +std::optional +ValueNode::Accessor::get(const ValueNode &obj) const +{ + std::optional cx = obj["cx"].get(); + std::optional cy = obj["cy"].get(); + std::optional k0 = obj["k0"].get(); + std::optional k1 = obj["k1"].get(); + std::optional k2 = obj["k2"].get(); + std::optional k3 = obj["k3"].get(); + std::optional k4 = obj["k4"].get(); + + if (!(cx && cy && k0 && k1 && k2 && k3 && k4)) { + LOG(LscPolynomial, Error) + << "Polynomial is missing a parameter"; + return std::nullopt; + } + + return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); +} +#endif /* __DOXYGEN__ */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index a0d27c4b21d6..d52ec86946b1 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -80,30 +80,4 @@ private: } /* namespace ipa */ -#ifndef __DOXYGEN__ - -template<> -struct ValueNode::Accessor { - std::optional get(const ValueNode &obj) const - { - std::optional cx = obj["cx"].get(); - std::optional cy = obj["cy"].get(); - std::optional k0 = obj["k0"].get(); - std::optional k1 = obj["k1"].get(); - std::optional k2 = obj["k2"].get(); - std::optional k3 = obj["k3"].get(); - std::optional k4 = obj["k4"].get(); - - if (!(cx && cy && k0 && k1 && k2 && k3 && k4)) { - LOG(LscPolynomial, Error) - << "Polynomial is missing a parameter"; - return std::nullopt; - } - - return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); - } -}; - -#endif - } /* namespace libcamera */ From patchwork Wed Aug 5 16:12:55 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27636 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 B8BB4C333F for ; Wed, 5 Aug 2026 16:13:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CB5D568154; Wed, 5 Aug 2026 18:13:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="pfjPFI7J"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0C93C68143 for ; Wed, 5 Aug 2026 18:13:33 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3E80139A2; Wed, 5 Aug 2026 18:12:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946342; bh=k/Zxb+dsBZMFvAdkrJe72HrvcKz5+1mdvrGEcvsl2bk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=pfjPFI7JXvJbDOGXgQpVKhApaxql2kOA17v9l4xnDJEHdrWF1LNh9IF6MWLZ7kKa8 H+zv4P2LTl5D+P+WokhG9IsHeO7Z7hhU8nCGwXcJOUIoovp8armE41PHSytAk0t/NW LyYrw+IdGwk5hejTiuh20u/8eD3LUWcfD+Fzv0bI= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:55 +0200 Subject: [PATCH v7 13/32] ipa: libipa: lsc_polynomial: Rename LscPolynomial MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-13-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=6305; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=k/Zxb+dsBZMFvAdkrJe72HrvcKz5+1mdvrGEcvsl2bk=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Edm3IABpWzEQXkh0gezcaH/2Obwtwbs6F+J KYONmrRhmCJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhHQAKCRByNAaPFqFW POu7EACkSLniTjJjnUHm+xK44cCICPisG7ZBqfGiucG9WeYt/kuUU0XQxj6BTjwZTHkSo4dPgnE +OKhgoN9hFnau1xEj45CpaU3dnEPBJaxjNqGZxsZueM9FHddKXOtM1873TQtGGvvd1xwekBngEb gvTOoMNIjy50zgR9G6JPQE70fjw9GNlT1/bH24yyC/c2RxK7QPjpFIR3Snz6JX10THnQOAbb6VR XBHCKqtQU7qGKnvwAHxuZmRlzzzJuguPfw6gYu2cDqPI15G9/LGA27YpZcinSeZuV1lvg21JrZv vv95OApdpbebwYCYzE6J+zCHVJW6ltmWj15LG7M6iakN1/73GWJlfQXvfh0CHPZzeX28SZLyeff aBT7syHjoH1FocD+OvR5W0oJsEerUviSw+V8xxoqmUlzL96HHvMLeugU1u/T3drun0huTZt73Yq 3MjdYb+OCgtNinsYSEnMyEDfT+Q2nrAU1CmCqrITDd8UjNGI+RgkJmdUMl1vghVDPipmTQy6QiA ecxeuIiKf2sS/xg43ueCIpGIE+F3/F26n9BEDAVHQf+6fxF83B58xyEawyymh3K1o0JEsbyVztb itOQKAQexvwHmAXWXMKx6nYOGn072YZSjN5CNiRpEvTXgBlG96MJRKGA1LaztmO9BdmIhlZlK+T ZHECKPL8flBW7/g== 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" In order to prepare to introduce the LscPolynomial class move the existing implementation of the polynomial representation LscPolynomial to lsc::Polynomial. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_polynomial.cpp | 20 ++++++++++++-------- src/ipa/libipa/lsc_polynomial.h | 12 ++++++++---- src/ipa/rkisp1/algorithms/lsc.cpp | 22 +++++++++++----------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index 921af0b0bd13..b93063bf8bc7 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -20,8 +20,10 @@ LOG_DEFINE_CATEGORY(LscPolynomial) namespace ipa { +namespace lsc { + /** - * \class LscPolynomial + * \class Polynomial * \brief Class for handling even polynomials used in lens shading correction * * Shading artifacts of camera lenses can be modeled using even radial @@ -31,7 +33,7 @@ namespace ipa { */ /** - * \fn LscPolynomial::LscPolynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, + * \fn Polynomial::Polynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, double k1 = 0.0, double k2 = 0.0, double k3 = 0.0, double k4 = 0.0) * \brief Construct a polynomial using the given coefficients @@ -45,7 +47,7 @@ namespace ipa { */ /** - * \fn LscPolynomial::sampleAtNormalizedPixelPos(double x, double y) + * \fn Polynomial::sampleAtNormalizedPixelPos(double x, double y) * \brief Sample the polynomial at the given normalized pixel position * * This functions samples the polynomial at the given pixel position divided by @@ -57,7 +59,7 @@ namespace ipa { */ /** - * \fn LscPolynomial::getM() + * \fn Polynomial::getM() * \brief Get the value m as described in the dng specification * * Returns m according to dng spec. m represents the Euclidean distance @@ -68,7 +70,7 @@ namespace ipa { */ /** - * \fn LscPolynomial::setReferenceImageSize(const Size &size) + * \fn Polynomial::setReferenceImageSize(const Size &size) * \brief Set the reference image size * * Set the reference image size that is used for subsequent calls to getM() and @@ -77,12 +79,14 @@ namespace ipa { * \param size The size of the reference image */ +} /* namespace lsc */ + } /* namespace ipa */ #ifndef __DOXYGEN__ template<> -std::optional -ValueNode::Accessor::get(const ValueNode &obj) const +std::optional +ValueNode::Accessor::get(const ValueNode &obj) const { std::optional cx = obj["cx"].get(); std::optional cy = obj["cy"].get(); @@ -98,7 +102,7 @@ ValueNode::Accessor::get(const ValueNode &obj) const return std::nullopt; } - return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); + return ipa::lsc::Polynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); } #endif /* __DOXYGEN__ */ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index d52ec86946b1..3be33edfe7d8 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -24,12 +24,14 @@ LOG_DECLARE_CATEGORY(LscPolynomial) namespace ipa { -class LscPolynomial +namespace lsc { + +class Polynomial { public: - LscPolynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, - double k1 = 0.0, double k2 = 0.0, double k3 = 0.0, - double k4 = 0.0) + Polynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0, + double k1 = 0.0, double k2 = 0.0, double k3 = 0.0, + double k4 = 0.0) : cx_(cx), cy_(cy), cnx_(0), cny_(0), coefficients_({ k0, k1, k2, k3, k4 }) { @@ -78,6 +80,8 @@ private: Size imageSize_; }; +} /* namespace lsc */ + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index af919f7429af..84d2b1d3bdac 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -65,10 +65,10 @@ class LscPolynomialImpl : public LscImplementation { private: struct PolynomialComponents { - LscPolynomial pr; - LscPolynomial pgr; - LscPolynomial pgb; - LscPolynomial pb; + lsc::Polynomial pr; + lsc::Polynomial pgr; + lsc::Polynomial pgb; + lsc::Polynomial pb; }; using PolynomialComponentsMap = std::map; @@ -87,7 +87,7 @@ public: private: std::vector sizesListToPositions(Span sizes); - std::vector samplePolynomial(const LscPolynomial &poly, + std::vector samplePolynomial(const lsc::Polynomial &poly, Span xPositions, Span yPositions, const Rectangle &cropRectangle); @@ -98,7 +98,7 @@ private: int LscPolynomialImpl::parseLscData(const ValueNode &sets) { for (const auto &set : sets.asList()) { - std::optional pr, pgr, pgb, pb; + std::optional pr, pgr, pgb, pb; uint32_t ct = set["ct"].get(0); if (lscData_.count(ct)) { @@ -108,10 +108,10 @@ int LscPolynomialImpl::parseLscData(const ValueNode &sets) return -EINVAL; } - pr = set["r"].get(); - pgr = set["gr"].get(); - pgb = set["gb"].get(); - pb = set["b"].get(); + pr = set["r"].get(); + pgr = set["gr"].get(); + pgb = set["gb"].get(); + pb = set["b"].get(); if (!(pr || pgr || pgb || pb)) { LOG(RkISP1Lsc, Error) @@ -161,7 +161,7 @@ LscPolynomialImpl::sampleForCrop(const Rectangle &cropRectangle, } std::vector -LscPolynomialImpl::samplePolynomial(const LscPolynomial &poly, +LscPolynomialImpl::samplePolynomial(const lsc::Polynomial &poly, Span xPositions, Span yPositions, const Rectangle &cropRectangle) From patchwork Wed Aug 5 16:12:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27637 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 45772C3340 for ; Wed, 5 Aug 2026 16:13:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CF34C68148; Wed, 5 Aug 2026 18:13:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="p8fwBCvM"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AF02868131 for ; Wed, 5 Aug 2026 18:13:33 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D6C2339AA; Wed, 5 Aug 2026 18:12:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946343; bh=+BLekKS2khj7QSEHhQsHfSrQMgwm055rzcXMP/0cFSk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=p8fwBCvMTms+97fQ4ei/HqAZZfmCpzb44KRFukY9TjDr3H1yNG1A/zxpkZW9ctrXC Dm4kjoQG7swvLkDWe1nvU1Gxb9vklPaVEuIgftSLVUcBF59DvZp+86zRDzAfpt0OEa HjumqInVnGgEqfeih5cNs+7+dfsOS87kPmC3NsOw= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:56 +0200 Subject: [PATCH v7 14/32] ipa: libipa: lsc_polynomial: Do not inline functions MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-14-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=4087; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=+BLekKS2khj7QSEHhQsHfSrQMgwm055rzcXMP/0cFSk=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Edvqe9kapT6BzUoVl8SAbfXSPDB0eKBmDD6 SI+7ideX6GJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhHQAKCRByNAaPFqFW PKCrD/9jpR8CYPdWMjN30pO43lDur4X82n/rADY5zF2U+lUjloOZIvnvZS7N9XiP+WKN5n4XYvN jAAUM48RMlJU5Wmkk0lM+o8YSdTm+8cCHiO1YK0rXlhVb4XgPJl73yAnvT06I/hfquPleqGmYoz /yGd3fAdnDfVx4SgkopC7nxrLH+C9C+5Rh1U8TDTxZiPpGOaqyBsI1zzCHLXDExWPgk/9dCgbqP hU70kYLHbjqxW8ISHZjCQpY032hrt2U8tb01/yx45ZCnh6v4Njsp35bD+8gilYd4AXpa9c3PtdS 95sklilyQGSqgRSQL4bZWJ6rytBZOMvU0+cKOpxNVcNkiPLEds/kbl8Wz8PPmBhUOjxZE4n5rZD QGrzDp2YWmyqsGlWNa+IMMUTFSIe3a0Z4tEECHFpbkjkFBRbEhdAYd6tFAov+2hk/CdGCtX3RGX JbKL6EiVQdMc5Ks2Vf/SWtZlZiXAE/4wuW4l+TjF6ZV87/TssLP2B27wLVE8NCJkuKagn2QmJnU BFNEzhfuWjBYChcgkJuySHsEAmYXOyoXclKjGmH2+ZRifTeBbGlKNLQUdPLS5eTyHFSHn76Dx5Q eAAl8tgi4zOEASdP53V48tKbab8PD70W+MtKXnlqzi1ZrfdQDc9nc3hKAdBA7nMQSxxIwB3bti1 HxV8I+lqqTEQjRQ== 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" There is no reason to inline the Polynomial class implementation. Move functions implementation to the corresponding .cpp file. While at it remove a rougue empty line between class members definitions. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_polynomial.cpp | 34 +++++++++++++++++++++++++++++++--- src/ipa/libipa/lsc_polynomial.h | 36 +++--------------------------------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index b93063bf8bc7..fb6f959e5a09 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -47,7 +47,6 @@ namespace lsc { */ /** - * \fn Polynomial::sampleAtNormalizedPixelPos(double x, double y) * \brief Sample the polynomial at the given normalized pixel position * * This functions samples the polynomial at the given pixel position divided by @@ -57,9 +56,20 @@ namespace lsc { * \param y y position in normalized coordinates * \return The sampled value */ +double Polynomial::sampleAtNormalizedPixelPos(double x, double y) const +{ + double dx = x - cnx_; + double dy = y - cny_; + double r = sqrt(dx * dx + dy * dy); + double res = 1.0; + + for (unsigned int i = 0; i < coefficients_.size(); i++) + res += coefficients_[i] * std::pow(r, (i + 1) * 2); + + return res; +} /** - * \fn Polynomial::getM() * \brief Get the value m as described in the dng specification * * Returns m according to dng spec. m represents the Euclidean distance @@ -68,9 +78,17 @@ namespace lsc { * * \return The sampled value */ +double Polynomial::getM() const +{ + double cpx = imageSize_.width * cx_; + double cpy = imageSize_.height * cy_; + double mx = std::max(cpx, std::fabs(imageSize_.width - cpx)); + double my = std::max(cpy, std::fabs(imageSize_.height - cpy)); + + return sqrt(mx * mx + my * my); +} /** - * \fn Polynomial::setReferenceImageSize(const Size &size) * \brief Set the reference image size * * Set the reference image size that is used for subsequent calls to getM() and @@ -78,6 +96,16 @@ namespace lsc { * * \param size The size of the reference image */ +void Polynomial::setReferenceImageSize(const Size &size) +{ + assert(!size.isNull()); + imageSize_ = size; + + /* Calculate normalized centers */ + double m = getM(); + cnx_ = (size.width * cx_) / m; + cny_ = (size.height * cy_) / m; +} } /* namespace lsc */ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 3be33edfe7d8..2caf46d2d759 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -37,38 +37,9 @@ public: { } - double sampleAtNormalizedPixelPos(double x, double y) const - { - double dx = x - cnx_; - double dy = y - cny_; - double r = sqrt(dx * dx + dy * dy); - double res = 1.0; - for (unsigned int i = 0; i < coefficients_.size(); i++) { - res += coefficients_[i] * std::pow(r, (i + 1) * 2); - } - return res; - } - - double getM() const - { - double cpx = imageSize_.width * cx_; - double cpy = imageSize_.height * cy_; - double mx = std::max(cpx, std::fabs(imageSize_.width - cpx)); - double my = std::max(cpy, std::fabs(imageSize_.height - cpy)); - - return sqrt(mx * mx + my * my); - } - - void setReferenceImageSize(const Size &size) - { - assert(!size.isNull()); - imageSize_ = size; - - /* Calculate normalized centers */ - double m = getM(); - cnx_ = (size.width * cx_) / m; - cny_ = (size.height * cy_) / m; - } + double sampleAtNormalizedPixelPos(double x, double y) const; + double getM() const; + void setReferenceImageSize(const Size &size); private: double cx_; @@ -76,7 +47,6 @@ private: double cnx_; double cny_; std::array coefficients_; - Size imageSize_; }; From patchwork Wed Aug 5 16:12:57 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27638 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 3A945C3341 for ; Wed, 5 Aug 2026 16:13:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A2B7068179; Wed, 5 Aug 2026 18:13:47 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="h5jI/CFO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 50CFD68146 for ; Wed, 5 Aug 2026 18:13:34 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7E6D81C76; Wed, 5 Aug 2026 18:12:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946343; bh=70FZhClw7mS5xy+Y1CktDRCjvCls/+hWNvF/+aVuNwI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=h5jI/CFOqJM3HJkfxZRUQ5cBIeJKcUALaJfPKUlA5P8XuER1Qw6+G2Mebr6GeXtdr PEHqWF23Z1Y8N6sdmL7+Rmd7+5O4SrM4YmQiDB44laDuQqWRwAziQ4hh02P7JVExUv rDOQ2bYM+xdUZzDDBgxXQHlb1Q4QMVHkjfjp88lU= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:57 +0200 Subject: [PATCH v7 15/32] ipa: libipa: lsc: Move Interpolator to lsc_base MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-15-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=3386; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=70FZhClw7mS5xy+Y1CktDRCjvCls/+hWNvF/+aVuNwI=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EevQmOdSkLdiSNwij4euB2Xkn0hBv3W1PM/ hQ+4HrXVFOJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhHgAKCRByNAaPFqFW PPNKEACuJiug6abIlx2HrSTmGxESmEc84/cx20FcCzYz9L2rvkp1Vw5U32jhjThA+vlcWoYMw/5 RlFbLRYdHh7jFUZ3R3OuN5Ip0aFymkXHs5KhBlQ/3o+KH7Hq0V6avX0naGqa4NoiBhlcoVaBEZR 6jEFySq4/gSREYRBc1JZmmr61g7Z+yNVBObop4NQwvU/84cxza1uud1XLMdjIW9zyr3JyGKzcG1 TL+ZKkMoc/0olOYgLcOtqgU8wKIDYNfOpRqk2o6ecFVp7fPP/og6YqWuxJY3yh2kgSPUmFVGADl KK7F7eyNMPOzmhjUwtZ8WNiK2Pqi2Ow7963ufyOizQwKBaRulbjNoIgUtm80euZmyLjdNOiK3Xk O15tVg/lAKcEmhOB15Mw+y2IgL0IoAHI62o7lPSvQGxkwu6+DM61ZtMJSq+LpzSLeCNbrUUDVZq rdqVWr26IjrjWgQVeekApIjkqN7rJYlG6HxGXzuxqxKoCAA//5utd0I1wsa6It/s1eO0As6O+7b nJwriESG27pxo1vhe3L8hbHd2VxssAHwR11vmeQABdAW78BLQHoUrU2JPVeYjEV7fBQbF/7T+2X mrsWTl3aLjTuCrusgULQYessN8SFu4SJFmxVgPq+PIGVIo2q8WO0NLov2AGLT1KqNOZdDPIx1ey 3aegkLAF2j1KWdg== 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" Move the Interpolator: specialization from the RkISP1 IPA to the lsc_base header. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_base.cpp | 15 +++++++++++++++ src/ipa/libipa/lsc_base.h | 21 +++++++++++++++++++++ src/ipa/rkisp1/algorithms/lsc.cpp | 27 --------------------------- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 26197f4effe5..b50460394925 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -49,6 +49,21 @@ namespace lsc { } /* namespace lsc */ +#ifndef __DOXYGEN__ +template<> +void Interpolator:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, + double lambda) +{ + interpolateVector(a.r, b.r, dest.r, lambda); + interpolateVector(a.gr, b.gr, dest.gr, lambda); + interpolateVector(a.gb, b.gb, dest.gb, lambda); + interpolateVector(a.b, b.b, dest.b, lambda); +} +#endif + /** * \class LscImplementation * \brief Pure virtual base class for LSC algorithm implementations diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 59b831e53fbb..ca7ad6e4051c 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -18,6 +18,8 @@ #include "libcamera/internal/value_node.h" +#include "interpolator.h" + namespace libcamera { namespace ipa { @@ -35,6 +37,25 @@ using ComponentsMap = std::map; } /* namespace lsc */ +#ifndef __DOXYGEN__ +template +void interpolateVector(const std::vector &a, const std::vector &b, + std::vector &dest, double lambda) +{ + ASSERT(a.size() == b.size()); + dest.resize(a.size()); + for (size_t i = 0; i < a.size(); i++) + dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda; +} + +template<> +void Interpolator:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, + double lambda); +#endif /* __DOXYGEN__ */ + class LscImplementation { public: diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 84d2b1d3bdac..8d92ee89cdaf 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -25,33 +25,6 @@ namespace libcamera { -namespace ipa { - -template -void interpolateVector(const std::vector &a, const std::vector &b, - std::vector &dest, double lambda) -{ - ASSERT(a.size() == b.size()); - dest.resize(a.size()); - for (size_t i = 0; i < a.size(); i++) - dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda; -} - -template<> -void Interpolator:: - interpolate(const lsc::Components &a, - const lsc::Components &b, - lsc::Components &dest, - double lambda) -{ - interpolateVector(a.r, b.r, dest.r, lambda); - interpolateVector(a.gr, b.gr, dest.gr, lambda); - interpolateVector(a.gb, b.gb, dest.gb, lambda); - interpolateVector(a.b, b.b, dest.b, lambda); -} - -} /* namespace ipa */ - namespace ipa::rkisp1::algorithms { From patchwork Wed Aug 5 16:12:58 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27639 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 6D7D6C3342 for ; Wed, 5 Aug 2026 16:13:49 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E31F968160; Wed, 5 Aug 2026 18:13:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BdH76dEM"; 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 D46A868148 for ; Wed, 5 Aug 2026 18:13:34 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1D90D39AC; Wed, 5 Aug 2026 18:12:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946344; bh=zV7xjj4E3N+lrt+zYVjWqNnwet3OpaLv/0Vj2kD9DjQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=BdH76dEMwAZ/+ZOw/HZ3J+5Puf7KYoSE4qpzB/C1w5QCVhTvxhLZgUZtIRO5LbsTy Cop4vdHWgm35V6jRcWk3IYiNOr1k2Hgqp0JkCLSA66Htfk2KcOMbagK2fm44A70sjl rQsyT+yJ1QBC5j7uhTnY0FLqDqS0D2IpJRCNXqaM= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:58 +0200 Subject: [PATCH v7 16/32] ipa: rkisp1: lsc: Remove rogue empty line MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-16-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=658; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=zV7xjj4E3N+lrt+zYVjWqNnwet3OpaLv/0Vj2kD9DjQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Ee4hAfxDPAa+RwxhAf0RbYG87MSfoR62/EV QweYl+SOT6JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhHgAKCRByNAaPFqFW PCMzD/9dT1pSbYJGIKVq1dCj2jgueJxnTAiisnimD3az7LmHBF3CI6/EfpUxR7ZLQGEp6RkBeKU GC95IbD5/lSP9Q5b8iduaPKsHXfInmNbuWEyvBMIVlI6l2nnYZcHQWPns6goEfBgCMAqKQGWGQJ KFjeGHrNUWvj3xsRhrqGJVgU3ArgqfRUl33MqrF941AWJP6162zTt6LiaDkJeGRy4kwVbYrQfdj FFMB0dGgGzAKRzJRc1kaROVf/fAukFyuR3lqlYKoDTkhj3MTyaol3v+OWXQ77yljLVDJ4OlIvEn v+72qNd4gTEYQqNN/XjUtXvNrz2+1/0TcJH52TKmmBV90b2BmZle6ZWj35CMILpoeRLAX15kUNs qy/kC0DeLhyO1kUe7jlVPaWhc+J+D7wfDGP7do4q3E9Cbu9yrLrU/DiircGKvvG0DWxn0GgiR+b jeEJvvGmrEGK2Bt18ijClnuTneet0D0yD/DlgA07R2v3jbDBcjF0FeBL7Ihdct1g1p1i52Wnwwy /FsCOqJtqiquFal+9vFzJBxbGRzhf/Q6YTutlWdt8kjSuvz+WQCkh3C5SIIggSHkY2af9uoS2cK hoioRnEmzmk+buUuxZFF1kUTXSyUKbRjeaGzZG5PApfdO4EbJC3yTlHkrflfTWOeHdo3Qhi+0GP hYmmeXxe8tN3jtg== 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" Remove an additional empty line. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/rkisp1/algorithms/lsc.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 8d92ee89cdaf..839525f16746 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -27,7 +27,6 @@ namespace libcamera { namespace ipa::rkisp1::algorithms { - LOG_DEFINE_CATEGORY(RkISP1Lsc) namespace { From patchwork Wed Aug 5 16:12:59 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27640 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 E089EC3334 for ; Wed, 5 Aug 2026 16:13:50 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8E57A68164; Wed, 5 Aug 2026 18:13:50 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BWSe1Pbk"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9A3BC6813C for ; Wed, 5 Aug 2026 18:13:35 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AB85039AE; Wed, 5 Aug 2026 18:12:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946345; bh=QL4noIprmQqcb0tBXch8xNe2gPnkldHuhJwnwMEQRBg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=BWSe1PbkSiBH6fZ8kWcFWYx0iBi81IEt7MAzMfGfImXENOoYtv5Lz3d8cRriZZ54i 97dURhSlTeXLfvhdKftxuSXbsSvJNvMpQDqjmrJjD9iYWZT1xTt1oYNVOLdx6AHtTn ZBh96k06ko2hAw4/BKuEey4uFAE4Ba8t5B02aPVk= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:12:59 +0200 Subject: [PATCH v7 17/32] ipa: libipa: lsc_polynomial: Move LscPolynomial from RkISP1 MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-17-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=14540; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=QL4noIprmQqcb0tBXch8xNe2gPnkldHuhJwnwMEQRBg=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EeFk28J73kdDE9dh7ERw66YiPaS3uL07o/X ysNLZx5oIyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhHgAKCRByNAaPFqFW PPNZEACQ+fFKZKsEP9LTmB4vR3wcAT1inUNCCnXdZ0kMtKENwPsE8Ok3CWvu/iaXOgdMrwntN13 OBq4+ad/8/BX1xhj1uB7oLXWmMrE23MSCKut5RFUiDIVMBJ0o7l9G+bKIMrSDwvPer2pSuTVsew xsDJZdikAnrMNlNAvgQJ81kNtyD0PIDycyhyXGKb23+tvsk5L+m7KWjPSkLQwd7EjE+sd6AJXt2 71jC23suu9ZfufnaUlMG+x3wWogSvmL9jBnP+Gjh0FGYALHUKOhjb0Ll/ie4YOGQTGtdvCslYqD s79vdkoiGnffe2M9JQFABVleixv0SdQs6RKaTDfKykWljc8E3ScVZDJL8KWXjQgTFyyGzEKcwBw Qeytp8/Y3MsktUeWjZVUS3X7ErFy1ebBfpAS/trjnKquBCBRCCFuYL2g0gFRCbP2fGPkzSHUosV NdFkE3571S1RHxYJOAUy3jUVYdpoR1RO4e3DVTyNUAZ+F7IjiSh0feK7H4pgSwGP1WhXXtybhGg sI4bsemzYfSXIMEQLe9HnXZ2xmn5j4KjFqitRiA2d0z1b7esu8QFtGqfS9i0n3HH+HvpwRqL71O Nmn+RNI9lHBgWfqkgIHJsypYEL4hLe6BtobgGI49wrSaISkWe2sKu3MwMF7DGcFSsqgkK6xeIMU BuImLfz8VaMj39A== 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" Move the LscPolynomial class to libipa from RkISP1. Rename the class from LscPolynomialImpl to a simpler LscPolynomial. The class is copied verbatim, only documentation has been added. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_polynomial.cpp | 179 +++++++++++++++++++++++++++++++++++++- src/ipa/libipa/lsc_polynomial.h | 46 ++++++++-- src/ipa/rkisp1/algorithms/lsc.cpp | 162 +--------------------------------- src/ipa/rkisp1/algorithms/lsc.h | 1 - 4 files changed, 218 insertions(+), 170 deletions(-) diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index fb6f959e5a09..f095a5a92024 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -2,11 +2,14 @@ /* * Copyright (C) 2024, Ideas On Board * - * Polynomial class to represent lens shading correction + * Polynomial based lens shading correction */ #include "lsc_polynomial.h" +#include +#include + #include /** @@ -109,6 +112,180 @@ void Polynomial::setReferenceImageSize(const Size &size) } /* namespace lsc */ +/** + * \class LscPolynomial + * \brief Radial Polynomial LSC algorithm implementation + * + * Polynomial-based LSC algorithm implementation. The LscPolynomial class + * implements LSC support using a Polynomial to represent the shading artifacts + * map. + * + * \sa LscImplementation + */ + +/** + * \fn LscPolynomial::LscPolynomial + * \param[in] sensorSize The physical sensor size + * + * Construct an LscPolynomial + */ + +/** + * \brief Parse polynomial LSC data + * \param[in] sets The tuning file content + * + * Parse the LSC data in polyomial form from the \a sets tuning data. + * + * \return 0 on success or a negative error number otherwise + */ +int LscPolynomial::parseLscData(const ValueNode &sets) +{ + for (const auto &set : sets.asList()) { + std::optional pr, pgr, pgb, pb; + uint32_t ct = set["ct"].get(0); + + if (lscData_.count(ct)) { + LOG(LscPolynomial, Error) + << "Multiple sets found for " + << "color temperature " << ct; + return -EINVAL; + } + + pr = set["r"].get(); + pgr = set["gr"].get(); + pgb = set["gb"].get(); + pb = set["b"].get(); + + if (!(pr || pgr || pgb || pb)) { + LOG(LscPolynomial, Error) + << "Failed to parse polynomial for " + << "colour temperature " << ct; + return -EINVAL; + } + + pr->setReferenceImageSize(sensorSize_); + pgr->setReferenceImageSize(sensorSize_); + pgb->setReferenceImageSize(sensorSize_); + pb->setReferenceImageSize(sensorSize_); + + lscData_.emplace(std::piecewise_construct, + std::forward_as_tuple(ct), + std::forward_as_tuple(PolynomialComponents{ *pr, *pgr, *pgb, *pb })); + } + + if (lscData_.empty()) { + LOG(LscPolynomial, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; +} + +/** + * \brief Re-sample the LSC components for \a cropRectangle + * \param[in] cropRectangle The sensor analogue crop rectangle + * \param[in] xSizes List of horizontal positions of the LSc grid nodes + * \param[in] ySizes List of vertical positions of the LSC grid nodes + * + * LSC tables have to be re-sampled every time a new sensor configuration is + * used, as each streaming session might use a different sensor crop rectangle. + * + * Polynomial LSC tables can be re-sampled for a given sensor frame resolution + * using a list of horizontal and vertical nodes that define the LSC grid on + * which the polynomial is re-sampled on. + * + * \a cropRectangle represents the size of the frame on which the LSC tables + * have to be re-sampled on. + * + * \a xSizes and \a ySizes represent the position of the grid nodes vertexes in + * the [0, 1] interval. In example an equally spaced grid of 16 nodes will have + * each segment of size 0.0625 and the list of nodes position will be + * [0, 0.0625, 0.125, 0.1875, ... , 1]. It is expected that the first position + * is 0 and the last position is 1. + */ +lsc::ComponentsMap +LscPolynomial::sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) +{ + std::vector xPos = sizesListToPositions(xSizes); + std::vector yPos = sizesListToPositions(ySizes); + + lsc::ComponentsMap components; + + for (const auto &[k, p] : lscData_) { + components[k] = { + samplePolynomial(p.pr, xPos, yPos, cropRectangle), + samplePolynomial(p.pgr, xPos, yPos, cropRectangle), + samplePolynomial(p.pgb, xPos, yPos, cropRectangle), + samplePolynomial(p.pb, xPos, yPos, cropRectangle) + }; + } + + return components; +} + +std::vector +LscPolynomial::samplePolynomial(const lsc::Polynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle) +{ + double m = poly.getM(); + double x0 = cropRectangle.x / m; + double y0 = cropRectangle.y / m; + double w = cropRectangle.width / m; + double h = cropRectangle.height / m; + std::vector samples; + + samples.reserve(xPositions.size() * yPositions.size()); + + for (double y : yPositions) { + for (double x : xPositions) { + double xp = x0 + x * w; + double yp = y0 + y * h; + /* + * The hardware uses 2.10 fixed point format and limits + * the legal values to [1..3.999]. Scale and clamp the + * sampled value accordingly. + */ + int v = static_cast( + poly.sampleAtNormalizedPixelPos(xp, yp) * + 1024); + v = std::clamp(v, 1024, 4095); + samples.push_back(v); + } + } + return samples; +} + +/* + * The rkisp1 LSC grid spacing is defined by the cell sizes on the top-left + * quadrant of the grid. This is then mirrored in hardware to the other + * quadrants. See parseSizes() for further details. For easier handling, this + * function converts the cell sizes of half the grid to a list of position of + * the whole grid (on one axis). Example: + * + * input: | 0.2 | 0.3 | + * output: 0.0 0.2 0.5 0.8 1.0 + */ +std::vector +LscPolynomial::sizesListToPositions(Span sizes) +{ + const int half = sizes.size(); + std::vector positions(half * 2 + 1); + double x = 0.0; + + positions[half] = 0.5; + for (int i = 1; i <= half; i++) { + x += sizes[half - i]; + positions[half - i] = 0.5 - x; + positions[half + i] = 0.5 + x; + } + + return positions; +} + } /* namespace ipa */ #ifndef __DOXYGEN__ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 2caf46d2d759..17c2c34499f0 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -2,25 +2,23 @@ /* * Copyright (C) 2024, Ideas On Board * - * Helper for radial polynomial used in lens shading correction. + * Polynomial based lens shading correction */ #pragma once -#include #include -#include -#include +#include +#include -#include #include #include #include "libcamera/internal/value_node.h" -namespace libcamera { +#include "lsc_base.h" -LOG_DECLARE_CATEGORY(LscPolynomial) +namespace libcamera { namespace ipa { @@ -52,6 +50,40 @@ private: } /* namespace lsc */ +class LscPolynomial : public LscImplementation +{ +private: + struct PolynomialComponents { + lsc::Polynomial pr; + lsc::Polynomial pgr; + lsc::Polynomial pgb; + lsc::Polynomial pb; + }; + using PolynomialComponentsMap = std::map; + +public: + LscPolynomial(const Size &sensorSize) + : sensorSize_(sensorSize) + { + } + + int parseLscData(const ValueNode &sets) override; + + lsc::ComponentsMap + sampleForCrop(const Rectangle &cropRectangle, + Span xSizes, + Span ySizes) override; + +private: + std::vector sizesListToPositions(Span sizes); + std::vector samplePolynomial(const lsc::Polynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle); + PolynomialComponentsMap lscData_; + Size sensorSize_; +}; + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 839525f16746..b161798763cb 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -33,166 +33,6 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -class LscPolynomialImpl : public LscImplementation -{ -private: - struct PolynomialComponents { - lsc::Polynomial pr; - lsc::Polynomial pgr; - lsc::Polynomial pgb; - lsc::Polynomial pb; - }; - using PolynomialComponentsMap = std::map; - -public: - LscPolynomialImpl(const Size &sensorSize) - : sensorSize_(sensorSize) - { - } - - int parseLscData(const ValueNode &sets) override; - - lsc::ComponentsMap - sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) override; - -private: - std::vector sizesListToPositions(Span sizes); - std::vector samplePolynomial(const lsc::Polynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle); - PolynomialComponentsMap lscData_; - Size sensorSize_; -}; - -int LscPolynomialImpl::parseLscData(const ValueNode &sets) -{ - for (const auto &set : sets.asList()) { - std::optional pr, pgr, pgb, pb; - uint32_t ct = set["ct"].get(0); - - if (lscData_.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for " - << "color temperature " << ct; - return -EINVAL; - } - - pr = set["r"].get(); - pgr = set["gr"].get(); - pgb = set["gb"].get(); - pb = set["b"].get(); - - if (!(pr || pgr || pgb || pb)) { - LOG(RkISP1Lsc, Error) - << "Failed to parse polynomial for " - << "colour temperature " << ct; - return -EINVAL; - } - - pr->setReferenceImageSize(sensorSize_); - pgr->setReferenceImageSize(sensorSize_); - pgb->setReferenceImageSize(sensorSize_); - pb->setReferenceImageSize(sensorSize_); - - lscData_.emplace(std::piecewise_construct, - std::forward_as_tuple(ct), - std::forward_as_tuple(PolynomialComponents{ *pr, *pgr, *pgb, *pb })); - } - - if (lscData_.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; -} - -lsc::ComponentsMap -LscPolynomialImpl::sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) -{ - std::vector xPos = sizesListToPositions(xSizes); - std::vector yPos = sizesListToPositions(ySizes); - - lsc::ComponentsMap components; - - for (const auto &[k, p] : lscData_) { - components[k] = { - samplePolynomial(p.pr, xPos, yPos, cropRectangle), - samplePolynomial(p.pgr, xPos, yPos, cropRectangle), - samplePolynomial(p.pgb, xPos, yPos, cropRectangle), - samplePolynomial(p.pb, xPos, yPos, cropRectangle) - }; - } - - return components; -} - -std::vector -LscPolynomialImpl::samplePolynomial(const lsc::Polynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle) -{ - double m = poly.getM(); - double x0 = cropRectangle.x / m; - double y0 = cropRectangle.y / m; - double w = cropRectangle.width / m; - double h = cropRectangle.height / m; - std::vector samples; - - samples.reserve(xPositions.size() * yPositions.size()); - - for (double y : yPositions) { - for (double x : xPositions) { - double xp = x0 + x * w; - double yp = y0 + y * h; - /* - * The hardware uses 2.10 fixed point format and limits - * the legal values to [1..3.999]. Scale and clamp the - * sampled value accordingly. - */ - int v = static_cast( - poly.sampleAtNormalizedPixelPos(xp, yp) * - 1024); - v = std::clamp(v, 1024, 4095); - samples.push_back(v); - } - } - return samples; -} - -/* - * The rkisp1 LSC grid spacing is defined by the cell sizes on the top-left - * quadrant of the grid. This is then mirrored in hardware to the other - * quadrants. See parseSizes() for further details. For easier handling, this - * function converts the cell sizes of half the grid to a list of position of - * the whole grid (on one axis). Example: - * - * input: | 0.2 | 0.3 | - * output: 0.0 0.2 0.5 0.8 1.0 - */ -std::vector -LscPolynomialImpl::sizesListToPositions(Span sizes) -{ - const int half = sizes.size(); - std::vector positions(half * 2 + 1); - double x = 0.0; - - positions[half] = 0.5; - for (int i = 1; i <= half; i++) { - x += sizes[half - i]; - positions[half - i] = 0.5 - x; - positions[half + i] = 0.5 + x; - } - - return positions; -} - class LscTableImpl : public LscImplementation { public: @@ -358,7 +198,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, * \todo: Most likely the reference frame should be native_size. * Let's wait how the internal discussions progress. */ - algo_ = std::make_unique(context.sensorInfo.activeAreaSize); + algo_ = std::make_unique(context.sensorInfo.activeAreaSize); ret = algo_->parseLscData(sets); } else { LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index b21c91c0d81f..2a428293cb2e 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include "libipa/interpolator.h" From patchwork Wed Aug 5 16:13:00 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27641 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 70F34C3343 for ; Wed, 5 Aug 2026 16:13:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CF7F46817E; Wed, 5 Aug 2026 18:13:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QgzqTNMr"; 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 447A168155 for ; Wed, 5 Aug 2026 18:13:36 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6F78B39B4; Wed, 5 Aug 2026 18:12:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946345; bh=yzIyzdHFtumfQK1OfN/dSu7+yfCCq5ymm9fUX0hykmw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=QgzqTNMrekTxX9Vk9Y8uuplIIs65PTC8+F0mzwz241g/m6PnY520tyc0fV4LuSh2O YvPun0+fbvgXGs+5MI1mzTYVJ/l9+ChDrjqcLSRI3DwinXhILYR/+GrE3Vc+50uwHW ADezR4UCw+BI0+AgHChnXotQfCZFkYAN0DXKVrfc= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:00 +0200 Subject: [PATCH v7 18/32] ipa: libipa: lsc_table: Move LscTable from RkISP1 MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-18-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=8370; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=yzIyzdHFtumfQK1OfN/dSu7+yfCCq5ymm9fUX0hykmw=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Efy7i3vT4KpFzAvasGiT2N1m6Ov5YPepZG1 dwyo1nYqnyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhHwAKCRByNAaPFqFW PO06EAC6mvKYhnAYysO05+kk1jRRz6/PfVW4NX50dmVpTgUFzJxqKILMSBknm7xEN0HBtwhcbI9 yVUBk8d7OXb+c/08hIvxpNqO1OUcJwK1nSY37A2GlaAfRdgUCePGU+lNhY1qohCsijkmR5mBNBH MUlTL+gIFmEQu2lbApc/VsDDnjwpTTVnCe7IXsRTLlM+AfYoUXFnMxINakdPyZdSJ6TI0LpQlR3 +poarOvzrBjulSzks5PsJT+ZV/z4cP296P1EXB6tMxsisyRj61FUdMWmjFHNaIwCJmQetDXmIF0 v0/z8PFC3XYmvKYJdxpVZM/3e01lKE8odQj63PTTIV/Xwut8b8BUvbT91NKgbetyY7jYSho0MmW Y0wo8wDs0oNLtRuYmmlYB915T44PjVR+HPm3vRW/MR44UaVbdW9NNpPU/5WegxjpJdRjWxQuyAq QHY2Vl1xyzf87KX/yz/GVoKZt9zP6LcY83tW6nEnSX6w8M5E6u7mGC9JusYLF7yQ5EYvadr2ZWY Co3ZPdWKkny6168HYqhtZdWSx4d5kkOI+ZQnW+5RNJInvEkA06AbxAROKKRAUcQQixml3QkyAdj Mk3Z3ghifBmtrKXY61xqqk9uygSM7WLCPPuvacpTHYp3NS0Bibs9T7WOVcg+PsbmKsm3KJKa3ji A9Z/6rZdizYrCQA== 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" Move the LscTableImpl class to libipa from RkISP1. Rename the class from LscTableImpl to a simpler LscTable. The class is copied verbatim, only documntation has been added. The implementation still contains RkISP1 specific data which will be removed once the class is generalized. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc_table.cpp | 95 +++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc_table.h | 50 +++++++++++++++++++++ src/ipa/libipa/meson.build | 2 + src/ipa/rkisp1/algorithms/lsc.cpp | 81 +-------------------------------- 4 files changed, 149 insertions(+), 79 deletions(-) diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp new file mode 100644 index 000000000000..47dff156963a --- /dev/null +++ b/src/ipa/libipa/lsc_table.cpp @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Table-based LSC implementation + */ + +#include "lsc_table.h" + +/* \todo Remove RkISP1 from libipa. */ +#include "linux/rkisp1-config.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(LscTable) + +namespace ipa { + +/** + * \class LscTable + * \brief Table based LSC algorithm implementation + * + * Table based LSC algorithm implementation. The LSCTable class implements LSC + * support using tabular LSC data. + * + * \sa LscImplementation + */ + +/** + * \brief Parse tabular LSC data + * \param[in] sets The tuning file content + * + * Parse the LSC data in tabular form from the \a sets tuning data. + * + * \return 0 on success or a negative error number otherwise + */ +int LscTable::parseLscData(const ValueNode &sets) +{ + for (const auto &set : sets.asList()) { + uint32_t ct = set["ct"].get(0); + + if (lscData_.count(ct)) { + LOG(LscTable, Error) + << "Multiple sets found for color temperature " + << ct; + return -EINVAL; + } + + lsc::Components components; + components.r = parseTable(set, "r"); + components.gr = parseTable(set, "gr"); + components.gb = parseTable(set, "gb"); + components.b = parseTable(set, "b"); + + if (components.r.empty() || components.gr.empty() || + components.gb.empty() || components.b.empty()) { + LOG(LscTable, Error) + << "Set for color temperature " << ct + << " is missing tables"; + return -EINVAL; + } + + lscData_.emplace(ct, std::move(components)); + } + + if (lscData_.empty()) { + LOG(LscTable, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; +} + +std::vector LscTable::parseTable(const ValueNode &tuningData, + const char *prop) +{ + static constexpr unsigned int kLscNumSamples = + RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + + std::vector table = + tuningData[prop].get>().value_or(utils::defopt); + if (table.size() != kLscNumSamples) { + LOG(LscTable, Error) + << "Invalid '" << prop << "' values: expected " + << kLscNumSamples + << " elements, got " << table.size(); + return {}; + } + + return table; +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h new file mode 100644 index 000000000000..0983b78c3f6b --- /dev/null +++ b/src/ipa/libipa/lsc_table.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Table-based Lsc implementation + */ +#pragma once + +#include + +#include +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "lsc_base.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(LscTable) + +namespace ipa { + +class LscTable : public LscImplementation +{ +public: + int parseLscData(const ValueNode &sets) override; + + lsc::ComponentsMap + sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, + [[maybe_unused]] Span xSizes, + [[maybe_unused]] Span ySizes) override + { + LOG(LscTable, Warning) + << "Tabular LSC data doesn't support resampling"; + return lscData_; + } + +private: + std::vector parseTable(const ValueNode &tuningData, + const char *prop); + + lsc::ComponentsMap lscData_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 5682212ce9f6..7d98da6d40dc 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -16,6 +16,7 @@ libipa_headers = files([ 'interpolator.h', 'lsc_base.h', 'lsc_polynomial.h', + 'lsc_table.h', 'lux.h', 'module.h', 'pwl.h', @@ -40,6 +41,7 @@ libipa_sources = files([ 'interpolator.cpp', 'lsc_base.cpp', 'lsc_polynomial.cpp', + 'lsc_table.cpp', 'lux.cpp', 'module.cpp', 'pwl.cpp', diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index b161798763cb..f32c46084dd1 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -17,6 +17,7 @@ #include "libcamera/internal/value_node.h" #include "libipa/lsc_polynomial.h" +#include "libipa/lsc_table.h" #include "linux/rkisp1-config.h" /** @@ -33,84 +34,6 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -class LscTableImpl : public LscImplementation -{ -public: - int parseLscData(const ValueNode &sets) override; - - lsc::ComponentsMap - sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, - [[maybe_unused]] Span xSizes, - [[maybe_unused]] Span ySizes) override - { - LOG(RkISP1Lsc, Warning) - << "Tabular LSC data doesn't support resampling"; - return lscData_; - } - -private: - std::vector parseTable(const ValueNode &tuningData, - const char *prop); - - lsc::ComponentsMap lscData_; -}; - -int LscTableImpl::parseLscData(const ValueNode &sets) -{ - for (const auto &set : sets.asList()) { - uint32_t ct = set["ct"].get(0); - - if (lscData_.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } - - lsc::Components components; - components.r = parseTable(set, "r"); - components.gr = parseTable(set, "gr"); - components.gb = parseTable(set, "gb"); - components.b = parseTable(set, "b"); - - if (components.r.empty() || components.gr.empty() || - components.gb.empty() || components.b.empty()) { - LOG(RkISP1Lsc, Error) - << "Set for color temperature " << ct - << " is missing tables"; - return -EINVAL; - } - - lscData_.emplace(ct, std::move(components)); - } - - if (lscData_.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; -} - -std::vector LscTableImpl::parseTable(const ValueNode &tuningData, - const char *prop) -{ - static constexpr unsigned int kLscNumSamples = - RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; - - std::vector table = - tuningData[prop].get>().value_or(utils::defopt); - if (table.size() != kLscNumSamples) { - LOG(RkISP1Lsc, Error) - << "Invalid '" << prop << "' values: expected " - << kLscNumSamples - << " elements, got " << table.size(); - return {}; - } - - return table; -} - std::vector parseSizes(const ValueNode &tuningData, const char *prop) { @@ -190,7 +113,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, std::string type = tuningData["type"].get("table"); if (type == "table") { LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data."; - algo_ = std::make_unique(); + algo_ = std::make_unique(); ret = algo_->parseLscData(sets); } else if (type == "polynomial") { LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data."; From patchwork Wed Aug 5 16:13:01 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27642 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 C6CC5C3344 for ; Wed, 5 Aug 2026 16:13:53 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3659F68174; Wed, 5 Aug 2026 18:13:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FU6SYaHm"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C9C2368139 for ; Wed, 5 Aug 2026 18:13:36 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1745739B6; Wed, 5 Aug 2026 18:12:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946346; bh=WYRREtMMsGnWekfpemz4LN4fohagCWU8KMsfNpvYOME=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=FU6SYaHmFr3as/44flwkg3FtpKa40n7Pz27u3Zrq4pYnPwkSOUzvy6y/roZiErK0T cUKpf0Pv22hBLf6tf5VbQL7pn17I9pfukHWJRx3OoDjgNmkFs1JcG03vPmYfQlnKxe XHeaYAaMEfd9ROTkqBWBIPmUbEerNS/g8C0NXVw4= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:01 +0200 Subject: [PATCH v7 19/32] ipa: libipa: lsc_base: Add sampleForCrop documentation MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-19-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=1459; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=WYRREtMMsGnWekfpemz4LN4fohagCWU8KMsfNpvYOME=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EfLZhGSiAn6+wiG5mnbncHJ6C/L3atCzBYD oQ4CZGdweOJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhHwAKCRByNAaPFqFW PPU5EACjbDOUYZoCWdoLforNCO7BPMDpzJZDXvWTVFj0+8GRDrMsYZ3kphNmsaXoiXmCuM4xwq5 b2V+BhtAkmasSmUPCU1feh1jcH2d6Cby1rl3l9g28xbj3Np0Ea5QpdWejp2VewhSj7BT6puePOr qXKQwYmtLqWY2kDT/6OkhRuJNaKW25uQx6gpS+D6Hn+rqf8sQKamm77OVF3tc0CiMGCR1MFvzzN 3EbNo2JmZ9nXZUjXQhlB8ge/8xzF7rEPoFZov93nNLDtuUyjPsvjc7ahw1tUcgQpXO8LNzoF236 WFKtP+Grj2wGntugQe5bDm2B1fkfql2hQmHsSuDoqkV1DpBEnww+Vg8hmpdKZYv/s+AHGuof1B2 cTn57VkYGTcyWxBEnsIE4OzyEkAna7aZBUPrONEMokwQaP3WIsBMGyQ7vAAUVJWKZGMbvPvG4ci mf2IIFlx5gQt5k0tEDpZJrgv7RQ8PYsWxVxH7C2yr5LFjazAexM2iEtu6RSuoVShhaTsK4SxOkU 3A4CMnh3WCwHJQxkIwX62SBzYQaTyvQPA8brHDgRWLhnuhky5vdR+lJ5rxKuoUlrood0pv953Xo zBvU7uLA6Sl2sUvcBjdgc1oGpP1HkcaQkVOMUTeCJsR8Qxn988rk4CrepuXcUxiSITC0DpxtiR3 +PYjU26P7/7Bxkw== 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" Now that we have two in-tree implementations of LscImplementation detail how the two perform lsc gain re-sampling. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc_base.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index b50460394925..1015cdc7cf4c 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -103,6 +103,21 @@ void Interpolator:: * each segment of size 0.0625 and the list of nodes position will be * [0, 0.0625, 0.125, 0.1875, ... , 1]. It is expected that the first position * is 0 and the last position is 1. + * + * LSC tables are expressed in two formats: + * - A list of gain values (LscTable) + * - A radial polynomial (LscPolynomial) + * + * Table-based LSC tables are generated using an image at a fixed resolution and + * can't at the moment be re-sampled when a different resolution is used for a + * streaming session. Re-sampling a grid LSC table will return the same table + * as loaded from the tuning file. + * + * \todo: Implement grid based re-sampling + * + * Polynomial configurations are more flexible and can be re-sampled for a given + * sensor frame resolution using a list of horizontal and vertical nodes that + * define the LSC grid on which the polynomial is re-sampled on. */ } /* namespace ipa */ From patchwork Wed Aug 5 16:13:02 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27643 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 70473C3345 for ; Wed, 5 Aug 2026 16:13:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E955A68163; Wed, 5 Aug 2026 18:13:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fSsSu7ow"; 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 641D06812C for ; Wed, 5 Aug 2026 18:13:37 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A313F3A0B; Wed, 5 Aug 2026 18:12:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946347; bh=PI18vF6Qd9YxvW90hLJk4stKyrnMT8Ytth65u7Y3/I4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=fSsSu7owbNWnSgiSqpUAnwKj9xcrgXUlErKi2HZLwF34BN0dP89c2oE6zrCPFkBny UK/QffSc6bF9HoaXLO/JWfn8mpygdo64kto6FC01Ocha1hUtJYIosGroOjCPaClCbG JYpUFQ2Lm1xfZj0WAjRnhopR3KviaVO/DaQAOl7g= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:02 +0200 Subject: [PATCH v7 20/32] ipa: libipa: lsc: Introduce LscAlgorithm MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-20-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=11537; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=PI18vF6Qd9YxvW90hLJk4stKyrnMT8Ytth65u7Y3/I4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Eg4VtxVvsfKKofcm+2afXytgbbKGBpIZfkG uFUjCLOlD+JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIAAKCRByNAaPFqFW PKVoD/93+wM6rOBDOgUjPBvGurvJnvDdqJbT/DKNPKh00Ow2JFv6IOj8OHs7ApBo01QJOOIfZny RlAO1p2Bi5IKf7UgfylaYHA8IXLYVXTQU0C1Np7MTjUlMeduo8ktO68YPnrMB/zbLB3FzJ2yKMr mLQ/K7T2eTHPRqSr5veFtiqd/N/GjgqRp+n871dMJBXl+j74q/A/nlTT1ZtW0R85N+vxZ89sNYj UH6f+YS91KpHus/WoUy2Os/boYo3qnEa4CXhTSIO7+jLazogUQXLPNs58uQ/jYi0Q1FKqd30bPM RRTFA0xLpJ0iCuzjG1b+DvZkugQlXfLZ9EtRMR2J5/r6EtwUSaSuNbVCg2ODOjOiO8fXTmbfe4V 9JF4YUgqJ8Bbo5yR//RPpXASP6F7AtFxZ1znreCrAE6HJy6TP5knVTZL3jfKprIuzJvFMvpeenh wuoVENtdZtE7yGTJBDPCgXks9mhKGH3LSU2oyIh8cli6xRjMzC1y7KrtFNYSqpOrYAUMrxslhOn GzOqofrfm9kAqSlgfCddLfXIiHdmRwHcMKBDjo43Jrp28VyV/IrHV3XwtYg37jVJzjQuwf/gnyU y19P1UVOkUn3+0LVD8Opbq1xDOtQ9f4leu7LxD/NEP8Ri1WOZWwkdUyDOpBgml4nD3vdeIJiXrA y7o3qFkUCpmi/BA== 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" Introduce the lsc.h and lsc.cpp files with the definition of the Lsc algorithm active state, frame context and the LscAlgorithm class. The LscAlgorithm class uses two backends (LscPolynomial and LscTable) to perform tuning file parsing and re-scaling of the LSC gains. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc.cpp | 253 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc.h | 71 +++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 326 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp new file mode 100644 index 000000000000..dd69c7a80b17 --- /dev/null +++ b/src/ipa/libipa/lsc.cpp @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithms + */ + +#include "lsc.h" + +#include + +#include + +#include "lsc_polynomial.h" +#include "lsc_table.h" + +/** + * \file lsc.h + * \brief libipa LSC algorithm + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Lsc) + +namespace ipa { + +namespace lsc { + +/** + * \struct ActiveState + * \brief The LSC active state + * + * \var ActiveState::enabled + * \brief Boolean flag for the LscAlgorithm enable status + */ + +/** + * \struct FrameContext + * \brief The LSC frame context + * + * \var FrameContext::enabled + * \brief Boolean flag for the LscAlgorithm enable status + * + * \var FrameContext::update + * \brief Boolean flag for the LscAlgorithm updated status + */ + +} /* namespace lsc */ + +/** + * \class LscAlgorithm + * \brief libIPA LSC algorithm implementation + * + * Due to the optical characteristics of the lens, the light intensity received + * by the sensor is not uniform. The Lens Shading Correction algorithm applies + * multipliers to all pixels to compensate for the lens shading effect. + * + * The LscAlgorithm implements the libipa Lens Shading Correction algorithm + * using an implementation of the LscImplementation interface. + * + * This class provides support for parsing the tuning file content and + * generates tables indexed by colour temperature to store per-colour-channel + * gains for the IPA algorithm to be able to program the LSC engine. + * + * The init() function parses the tuning file and loads the gain tables either + * in tabular form (LscTable) or as radial polynomials (LscPolynomial). The gain + * tables are indexed by colour temperature with per-colour-components vectors + * of gain values or polynomial coefficients. + * + * At LscAlgorithm::configure() time the LSC tables are re-sampled on the + * sensor's crop rectangle in use to adapt them to the configuration in use for + * a streaming session. Polynomial LSC tables support re-sampling and can be + * applied to any sensor configuration. Grid-based LSC tables cannot currently + * be re-sampled and the configuration as parsed from the tuning file is used + * for all sensor configurations providing best-effort results. + * + * \todo Implement grid based re-sampling + * + * When the IPA algorithms wants to get access to the (re-sampled) tables to + * program its LSC engine, it uses LscAlgorithm::interpolateComponents() to get + * an LSC table interpolated by the LscAlgorithm class for the specified colour + * temperature. If the algorithm wants to access the non-interpolated tables it + * can retrieve them using LscAlgorithm::getComponents(). + */ + +/** + * \param[in] tuningData The tuning data + * \param[in] sensorSize The physical sensor size + * \param[in] controls The IPA list of supported controls + * + * Parse \a tuningData according to the settings specified in \a descriptor to + * populate the LSC data and registers LSC controls in \a controls. + * + * \return 0 on success, a negative error code otherwise + */ +int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize, + ControlInfoMap::Map &controls) +{ + polynomial_ = false; + + std::string type = tuningData["type"].get("table"); + if (type == "table") { + impl_ = std::make_unique(); + LOG(Lsc, Debug) << "Using table-based Lsc"; + } else if (type == "polynomial") { + /* + * \todo Most likely the reference frame should be native_size. + * Let's wait how the internal discussions progress. + */ + impl_ = std::make_unique(sensorSize); + polynomial_ = true; + LOG(Lsc, Debug) << "Using polynomial Lsc"; + } else { + LOG(Lsc, Error) << "Unsupported Lsc algorithm '" + << type << "'"; + return -EINVAL; + } + + const ValueNode &yamlSets = tuningData["sets"]; + if (!yamlSets.isList()) { + LOG(Lsc, Error) << "'sets' parameter not found in tuning file"; + return -EINVAL; + } + + int ret = impl_->parseLscData(yamlSets); + if (ret) + return ret; + + controls[&controls::LensShadingCorrectionEnable] = + ControlInfo(false, true, true); + + return 0; +} + +/** + * \param[in] state The LSC active state + * \param[in] analogCrop The current sensor analog crop rectangle + * \param[in] xPos List of horizontal positions of the LSC grid nodes + * \param[in] yPos List of vertical positions of the LSC grid nodes + * + * Re-sample the LSC data for an \a analogCrop. + * + * LSC tables are generated at tuning time using a known sensor configuration. + * When a new streaming session is started, it might use a different sensor + * configuration for which the LSC tables need to be adjusted to. + * + * This function re-generates the LSC tables to adapt them to a new sensor + * configuration, specifically it re-samples the LSC data for a new \a + * analogCrop on a grid specified by \a xPos and \a yPos. Re-sampling of + * LSC data is currently supported by polynomial-based LSC tables. + * + * \sa LscImplementation::sampleForCrop + * + * \return 0 on success, a negative error code otherwise + */ +int LscAlgorithm::configure(lsc::ActiveState &state, const Rectangle &analogCrop, + const std::vector &xPos, + const std::vector &yPos) +{ + LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop; + lsc::ComponentsMap lscData = + impl_->sampleForCrop(analogCrop, xPos, yPos); + + /* + * Retain a copy of the components table. + * + * We could avoid a copy here if getComponents() could + * return sets_.data() but I wasn't able to work around the + * compiler refusing it. + */ + lscData_ = lscData; + + sets_.setData(std::move(lscData)); + state.enabled = true; + + return 0; +} + +/** + * \brief Queue a request to the lsc algorithm + * \param[in] state The lsc active state + * \param[in] context The lsc frame context + * \param[in] controls The list of controls associated with a Request + * + * Queue a new list of \a controls to the lsc algorithm. + * The only supported control is controls::LensShadingCorrectionEnable. + */ +void LscAlgorithm::queueRequest(lsc::ActiveState &state, + lsc::FrameContext &context, + const ControlList &controls) +{ + const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); + if (lscEnable && *lscEnable != state.enabled) { + state.enabled = *lscEnable; + + LOG(Lsc, Debug) + << (state.enabled ? "Enabling" : "Disabling") << " Lsc"; + + context.update = true; + } + + context.enabled = state.enabled; +} + +/** + * \brief Populate the list of lsc metadata + * \param[in] context The lsc frame context + * \param[in] metadata The list of metadata + * + * Populates the list of \a metadata with controls handled by the LscAlgorithm + * class. The only supported metadata is controls::LensShadingCorrectionEnable. + */ +void LscAlgorithm::process(lsc::FrameContext &context, ControlList &metadata) +{ + metadata.set(controls::LensShadingCorrectionEnable, context.enabled); +} + +/** + * \fn LscAlgorithm::interpolateComponents + * \brief Interpolate the LSC tables for a given colour temperature + * \param[in] ct The colour temperature + * + * LSC tables are generated using different colour temperatures during the + * tuning phase. + * + * This function returns the interpolated LSC data for a given \a ct + * colour temperature. + * + * IPA algorithm can use this function to obtain a list of per-colour-component + * gains to program their LSC engines with every time a significant enough + * change in colour temperature is detected. + * + * Calling this function is only valid after LscAlgorithm::configure() has been + * called. An empty components list is returned otherwise. + * + * \return The LSC gains table interpolated for temperature \a ct + */ + +/** + * \fn LscAlgorithm::getComponents + * + * Return the map of LSC data per-colour-temperature. + * + * Calling this function is only valid after LscAlgorithm::configure() has been + * called. An empty components list is returned otherwise. + * + * \return The map of LSC gains tables per-colour-temperature + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h new file mode 100644 index 000000000000..9a51d32c3464 --- /dev/null +++ b/src/ipa/libipa/lsc.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithm + */ + +#pragma once + +#include +#include + +#include +#include + +#include "libcamera/internal/value_node.h" + +#include "interpolator.h" +#include "lsc_base.h" + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +struct ActiveState { + bool enabled; +}; + +struct FrameContext { + bool enabled; + bool update; +}; + +} /* namespace lsc */ + +class LscAlgorithm +{ +public: + int init(const ValueNode &tuningData, const Size &sensorSize, + ControlInfoMap::Map &controls); + + int configure(lsc::ActiveState &state, const Rectangle &analogCrop, + const std::vector &xPos, + const std::vector &yPos); + + void queueRequest(lsc::ActiveState &state, lsc::FrameContext &context, + const ControlList &controls); + void process(lsc::FrameContext &context, ControlList &metadata); + + const lsc::Components interpolateComponents(unsigned int ct) + { + return sets_.getInterpolated(ct); + } + + const lsc::ComponentsMap getComponents() + { + return lscData_; + } + +private: + std::unique_ptr impl_; + Interpolator sets_; + lsc::ComponentsMap lscData_; + bool polynomial_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 7d98da6d40dc..cbfa7cd40989 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -14,6 +14,7 @@ libipa_headers = files([ 'fixedpoint.h', 'histogram.h', 'interpolator.h', + 'lsc.h', 'lsc_base.h', 'lsc_polynomial.h', 'lsc_table.h', @@ -39,6 +40,7 @@ libipa_sources = files([ 'fixedpoint.cpp', 'histogram.cpp', 'interpolator.cpp', + 'lsc.cpp', 'lsc_base.cpp', 'lsc_polynomial.cpp', 'lsc_table.cpp', From patchwork Wed Aug 5 16:13:03 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27644 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 5EABCC3346 for ; Wed, 5 Aug 2026 16:13:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id ADD2B6818B; Wed, 5 Aug 2026 18:13:54 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WhcFphlE"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1B0FE68130 for ; Wed, 5 Aug 2026 18:13:38 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 46D5A3A0C; Wed, 5 Aug 2026 18:12:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946347; bh=521bH3mFquNCYSVRsyGnYMHj+jq/CjYM/hPvQbe+/a8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=WhcFphlESBAi7wiggWYG1NgM2EBUaYX6tFQvTFq9md/1C5akkYfkmRsXRWw0/elQg R+1VV9uBsoMFrPlrngXlMYk5aetTBNyd6KMRLZ0fg5cKUBRPFuIBtz08EwDRdWD8NZ 7uqufYiu98etdgYNHr4PpD9TpR4MQf211G5OBDcw= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:03 +0200 Subject: [PATCH v7 21/32] ipa: libipa: lsc: Move sizesListToPositions() back to rkisp1 MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-21-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=9723; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=521bH3mFquNCYSVRsyGnYMHj+jq/CjYM/hPvQbe+/a8=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EgKsMHsqTqUXUsh3hj0ivrAIY8ndTZ3g1At y9P1dSZnLaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIAAKCRByNAaPFqFW PP/YEACjsWJSgePA+y6hBZZiKg6ja/rhwqHJMQLvNmlNMdgFFOscYl5E5yTESUg2jZl2o+GnhzW pZRK8D408qgoc8tclVrZ/3la+6yKgzm+/gUSwlzPvoP3nS11v1oh2Z4fdmMR/Yh6g9uwwldlN/o edsIEpYNW0ve85WE9mtE0VIUqgRvkOBmgLimLv7pv++nkEdh2E5rxFjFnGZmIIXvepYY6mvlUk9 /mzJH8YkKuKT7d2yCognZSZRANRE5g6jdFICg8KC69C06Y6cQMK+QZq5I4Ci+dp29cLTQ4Vp9CR LaR8MBULeg5Et0uiJAYP73ZGKJ23+iLqiQ+5Sqo2hSMpuvv95FDMnRJWn+wZBxSFOQ+rq56KlHg Sh+1qnsc7DIexCCMNrQcCPwZj4TH0GXiU6qq+nAHSNDbMnf+gbtkkyDPjrFetPzl+Vey9V27Tba K7H/FLLfleaY+vf9Cv9wjEZJY7JP77dIERLMkEsGqW8MuSaY1epfdWSPY45tCCGTTlm02xr4HGq 76xaQ6TixOp7IBghAs0qeeARme5/xFgvDwGP7cMHDRfNm4X3lVNWhQ7j+mi5pyND7EDimnm3KMQ 10d7mX9SN9/bGrmmFigIqhRRGj9TB9Aqhpe5VnAYfRob6UmSOMypob5TCgsjy0uK1+UfUPRTytR PFU3aiKGM/NZslA== 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" The LscPolynomial::sampleForCrop() function is designed to receive a list of segment sizes and translates it to a list of grid vertexes internally with the sizesListToPositions() function. As the sizesListToPositions() implementation is RkISP1-specific, move it back to the RkISP1 IPA as a static helper and change the prototype of the sampleForCrop() function to accept a vector of grid vertexes instead of a span of segment sizes. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc_base.cpp | 6 +++--- src/ipa/libipa/lsc_base.h | 3 +-- src/ipa/libipa/lsc_polynomial.cpp | 38 ++++---------------------------------- src/ipa/libipa/lsc_polynomial.h | 4 +--- src/ipa/libipa/lsc_table.h | 4 ++-- src/ipa/rkisp1/algorithms/lsc.cpp | 31 ++++++++++++++++++++++++++++++- src/ipa/rkisp1/algorithms/lsc.h | 2 ++ 7 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 1015cdc7cf4c..b3671376a12a 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -88,8 +88,8 @@ void Interpolator:: * \fn LscImplementation::sampleForCrop * \brief Re-sample the LSC components for \a cropRectangle * \param[in] cropRectangle The sensor analogue crop rectangle - * \param[in] xSizes List of horizontal positions of the LSC grid nodes - * \param[in] ySizes List of vertical positions of the LSC grid nodes + * \param[in] xPos List of horizontal positions of the LSC grid nodes + * \param[in] yPos List of vertical positions of the LSC grid nodes * * LSC tables have to be re-sampled every time a new sensor configuration is * used, as each streaming session might use a different sensor crop rectangle @@ -98,7 +98,7 @@ void Interpolator:: * \a cropRectangle represents the size of the frame on which the LSC tables * have to be re-sampled on. * - * \a xSizes and \a ySizes represent the position of the grid nodes vertexes in + * \a xPos and \a yPos represent the position of the grid nodes vertexes in * the [0, 1] interval. For example an equally spaced grid of 16 nodes will have * each segment of size 0.0625 and the list of nodes position will be * [0, 0.0625, 0.125, 0.1875, ... , 1]. It is expected that the first position diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index ca7ad6e4051c..1bdb7d5c8ed9 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -65,8 +65,7 @@ public: virtual lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) = 0; + std::vector xPos, std::vector yPos) = 0; }; } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index f095a5a92024..682e2f14fe09 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -184,8 +184,8 @@ int LscPolynomial::parseLscData(const ValueNode &sets) /** * \brief Re-sample the LSC components for \a cropRectangle * \param[in] cropRectangle The sensor analogue crop rectangle - * \param[in] xSizes List of horizontal positions of the LSc grid nodes - * \param[in] ySizes List of vertical positions of the LSC grid nodes + * \param[in] xPos List of horizontal positions of the LSC grid nodes + * \param[in] yPos List of vertical positions of the LSC grid nodes * * LSC tables have to be re-sampled every time a new sensor configuration is * used, as each streaming session might use a different sensor crop rectangle. @@ -197,7 +197,7 @@ int LscPolynomial::parseLscData(const ValueNode &sets) * \a cropRectangle represents the size of the frame on which the LSC tables * have to be re-sampled on. * - * \a xSizes and \a ySizes represent the position of the grid nodes vertexes in + * \a xPos and \a yPos represent the position of the grid nodes vertexes in * the [0, 1] interval. In example an equally spaced grid of 16 nodes will have * each segment of size 0.0625 and the list of nodes position will be * [0, 0.0625, 0.125, 0.1875, ... , 1]. It is expected that the first position @@ -205,11 +205,8 @@ int LscPolynomial::parseLscData(const ValueNode &sets) */ lsc::ComponentsMap LscPolynomial::sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) + std::vector xPos, std::vector yPos) { - std::vector xPos = sizesListToPositions(xSizes); - std::vector yPos = sizesListToPositions(ySizes); lsc::ComponentsMap components; @@ -259,33 +256,6 @@ LscPolynomial::samplePolynomial(const lsc::Polynomial &poly, return samples; } -/* - * The rkisp1 LSC grid spacing is defined by the cell sizes on the top-left - * quadrant of the grid. This is then mirrored in hardware to the other - * quadrants. See parseSizes() for further details. For easier handling, this - * function converts the cell sizes of half the grid to a list of position of - * the whole grid (on one axis). Example: - * - * input: | 0.2 | 0.3 | - * output: 0.0 0.2 0.5 0.8 1.0 - */ -std::vector -LscPolynomial::sizesListToPositions(Span sizes) -{ - const int half = sizes.size(); - std::vector positions(half * 2 + 1); - double x = 0.0; - - positions[half] = 0.5; - for (int i = 1; i <= half; i++) { - x += sizes[half - i]; - positions[half - i] = 0.5 - x; - positions[half + i] = 0.5 + x; - } - - return positions; -} - } /* namespace ipa */ #ifndef __DOXYGEN__ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 17c2c34499f0..d2276ecc6f6b 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -71,11 +71,9 @@ public: lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) override; + std::vector xPos, std::vector yPos) override; private: - std::vector sizesListToPositions(Span sizes); std::vector samplePolynomial(const lsc::Polynomial &poly, Span xPositions, Span yPositions, diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index 0983b78c3f6b..67c7464ea5b6 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -30,8 +30,8 @@ public: lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, - [[maybe_unused]] Span xSizes, - [[maybe_unused]] Span ySizes) override + [[maybe_unused]] std::vector xPos, + [[maybe_unused]] std::vector yPos) override { LOG(LscTable, Warning) << "Tabular LSC data doesn't support resampling"; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index f32c46084dd1..f52516aa6ce7 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -64,6 +64,32 @@ std::vector parseSizes(const ValueNode &tuningData, return sizes; } +/* + * The rkisp1 LSC grid spacing is defined by the cell sizes on the top-left + * quadrant of the grid. This is then mirrored in hardware to the other + * quadrants. See parseSizes() for further details. For easier handling, this + * function converts the cell sizes of half the grid to a list of position of + * the whole grid (on one axis). Example: + * + * input: | 0.2 | 0.3 | + * output: 0.0 0.2 0.5 0.8 1.0 + */ +std::vector sizesListToPositions(Span sizes) +{ + const int half = sizes.size(); + std::vector positions(half * 2 + 1); + double x = 0.0; + + positions[half] = 0.5; + for (int i = 1; i <= half; i++) { + x += sizes[half - i]; + positions[half - i] = 0.5 - x; + positions[half + i] = 0.5 + x; + } + + return positions; +} + unsigned int quantize(unsigned int value, unsigned int step) { return std::lround(value / static_cast(step)) * step; @@ -100,6 +126,9 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, if (xSize_.empty() || ySize_.empty()) return -EINVAL; + xPos_ = sizesListToPositions(xSize_); + yPos_ = sizesListToPositions(ySize_); + /* Get all defined sets to apply. */ const ValueNode &sets = tuningData["sets"]; if (!sets.isList()) { @@ -171,7 +200,7 @@ int LensShadingCorrection::configure(IPAContext &context, LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop; lsc::ComponentsMap shadingData = algo_->sampleForCrop(configInfo.analogCrop, - xSize_, ySize_); + xPos_, yPos_); sets_.setData(std::move(shadingData)); context.activeState.lsc.enabled = true; diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 2a428293cb2e..b9fbc7df20ca 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -42,6 +42,8 @@ private: std::vector xSize_; std::vector ySize_; + std::vector xPos_; + std::vector yPos_; uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t yGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; uint16_t xSizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; From patchwork Wed Aug 5 16:13:04 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27645 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 4FB96C3308 for ; Wed, 5 Aug 2026 16:13:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EBD3E68189; Wed, 5 Aug 2026 18:13:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="aC0Pqs4z"; 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 B6F7868138 for ; Wed, 5 Aug 2026 18:13:38 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DBFD8929; Wed, 5 Aug 2026 18:12:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946348; bh=mGI35EtijXnwR3eGBLAvuPQc9uD9Epa859QBaNr5PvA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=aC0Pqs4zdMnSAdJkWvZlv+kN5AIKurM6Q8E/9EQ9kNWAXRiOMSl+lVoldkJJUBVur hT1SVrMSGpIQXIt5sCFloG3xUaZ0B9koJRVlBT8T6M/jGigUflvF2efK9zKG9A/knw +mtdKGKTXmP6Gk7qL5jyvBFuwhgfpcuNsdcTLTQ0= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:04 +0200 Subject: [PATCH v7 22/32] ipa: rkisp1: lsc: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-22-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=5909; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=mGI35EtijXnwR3eGBLAvuPQc9uD9Epa859QBaNr5PvA=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EgkN8Gja6KGkKDUHZ9EBh2hrh18GYObQlcv a4Ra/wfLt+JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIAAKCRByNAaPFqFW PB6mD/9okVsYwizMJBmyO47iF7KTA60DLf24ZEuLQFDlK7PZy+SNOoSNzNq89v+tXPZBGx30MtB WN11eXtQER/RtjSVqPkh9DhLXjfOYMM+06SD5OJHXTwQ6YjiTJ48pRmImF9WL1vrRIHlZZpdN+z UDCvnpxj6RSG/gXyUKiZIvdNf1EPB5BGn/fHViitd+kGe9bI1DVWk//D4JuNTssgFbNY507Z8ci CY8B3XiyZXBwyJ+iy1vg/bf1hKhMi5kxA0xFX1TC0VggdmW6qeFW/QTBgjlAK6iSBqlsAo/gSzX HK/KkZgPZwKYitw70x4ZJt/VyGcJJkWhbbdRbxqXFHEBjGyDE3esJLiyPgpZbBzxvBjUrDSouVX Wwh8jfHOjWu+nsxVHJ0FhEbzdr81ZL+U+4HNlQ7CYKE3NYCyLhJjy5zSgC4+UvJOg74joC49IGH oxes8wnyrKFlpM/Jg83KYT13DOs/ZqWw4aX1UndHljslnIoqm5R21Q08ijPZBlQ3rjXwjXCf22N kEBLtEYvWSxe+FxQud/lszlJo1x8uOK0BF9rMr6vhsla1bbgdEoVu3M70KKP9RuzP9932QW1vOG SHwmK4p4gl2pX/X8atOXpp6wiiIDhFb0bed0XxsBizuPltBvRgO8zSiRTQmQxSP/Osw4Owe1zZF n3+CyzwO+BeclnQ== 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" Port the RkISP1 LSC algorithm to use the LscAlgorithm class by composition. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/rkisp1/algorithms/lsc.cpp | 72 +++++---------------------------------- src/ipa/rkisp1/algorithms/lsc.h | 13 +++---- src/ipa/rkisp1/ipa_context.h | 10 ++---- 3 files changed, 18 insertions(+), 77 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index f52516aa6ce7..b0d6649be4fd 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -14,12 +14,6 @@ #include #include -#include "libcamera/internal/value_node.h" - -#include "libipa/lsc_polynomial.h" -#include "libipa/lsc_table.h" -#include "linux/rkisp1-config.h" - /** * \file lsc.h */ @@ -129,42 +123,8 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, xPos_ = sizesListToPositions(xSize_); yPos_ = sizesListToPositions(ySize_); - /* Get all defined sets to apply. */ - const ValueNode &sets = tuningData["sets"]; - if (!sets.isList()) { - LOG(RkISP1Lsc, Error) - << "'sets' parameter not found in tuning file"; - return -EINVAL; - } - - int ret; - - std::string type = tuningData["type"].get("table"); - if (type == "table") { - LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data."; - algo_ = std::make_unique(); - ret = algo_->parseLscData(sets); - } else if (type == "polynomial") { - LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data."; - /* - * \todo: Most likely the reference frame should be native_size. - * Let's wait how the internal discussions progress. - */ - algo_ = std::make_unique(context.sensorInfo.activeAreaSize); - ret = algo_->parseLscData(sets); - } else { - LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" - << type << "'"; - ret = -EINVAL; - } - - if (ret) - return ret; - - context.ctrlMap[&controls::LensShadingCorrectionEnable] = - ControlInfo(false, true, true); - - return 0; + return lscAlgo_.init(tuningData, context.sensorInfo.activeAreaSize, + context.ctrlMap); } /** @@ -198,13 +158,8 @@ int LensShadingCorrection::configure(IPAContext &context, yGrad_[i] = std::round(32768 / ySizes_[i]); } - LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop; - lsc::ComponentsMap shadingData = algo_->sampleForCrop(configInfo.analogCrop, - xPos_, yPos_); - sets_.setData(std::move(shadingData)); - - context.activeState.lsc.enabled = true; - return 0; + return lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop, + xPos_, yPos_); } void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) @@ -232,19 +187,8 @@ void LensShadingCorrection::queueRequest(IPAContext &context, IPAFrameContext &frameContext, const ControlList &controls) { - auto &lsc = context.activeState.lsc; - - const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); - if (lscEnable && *lscEnable != lsc.enabled) { - lsc.enabled = *lscEnable; - - LOG(RkISP1Lsc, Debug) - << (lsc.enabled ? "Enabling" : "Disabling") << " Lsc"; - - frameContext.lsc.update = true; - } - - frameContext.lsc.enabled = lsc.enabled; + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, + controls); } /** @@ -282,7 +226,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, setParameters(*config); - const lsc::Components &set = sets_.getInterpolated(quantizedCt); + const lsc::Components &set = lscAlgo_.interpolateComponents(quantizedCt); copyTable(*config, set); lastAppliedCt_ = ct; @@ -302,7 +246,7 @@ void LensShadingCorrection::process([[maybe_unused]] IPAContext &context, [[maybe_unused]] const rkisp1_stat_buffer *stats, ControlList &metadata) { - metadata.set(controls::LensShadingCorrectionEnable, frameContext.lsc.enabled); + lscAlgo_.process(frameContext.lsc, metadata); } REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection") diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index b9fbc7df20ca..63f0887f0838 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -7,10 +7,13 @@ #pragma once -#include +#include -#include "libipa/interpolator.h" -#include "libipa/lsc_base.h" +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/lsc.h" #include "algorithm.h" @@ -52,9 +55,7 @@ private: unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; - ipa::Interpolator sets_; - - std::unique_ptr algo_; + LscAlgorithm lscAlgo_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 6698ccfd77cf..7b4346c54ed2 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -30,6 +30,7 @@ #include "libipa/ccm.h" #include "libipa/fc_queue.h" #include "libipa/fixedpoint.h" +#include "libipa/lsc.h" namespace libcamera { @@ -141,9 +142,7 @@ struct IPAActiveState { double strength; } wdr; - struct { - bool enabled; - } lsc; + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -217,10 +216,7 @@ struct IPAFrameContext : public FrameContext { double gain; } wdr; - struct { - bool enabled; - bool update; - } lsc; + ipa::lsc::FrameContext lsc; }; struct IPAContext { From patchwork Wed Aug 5 16:13:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27646 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 776D3C3347 for ; Wed, 5 Aug 2026 16:13:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0F7CD68188; Wed, 5 Aug 2026 18:13:57 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="H8wKa6w0"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 787C36815A for ; Wed, 5 Aug 2026 18:13:39 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8A9CB3A1A; Wed, 5 Aug 2026 18:12:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946349; bh=v4v4Eg4kSgf7KzA8URy4yQTBrx03pOq6Cpvt3Zh6qPA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=H8wKa6w09FIlg2QdAe63eCQ+XxreP0RPD3CAQL4KxXZTo0ji27qcnS8LoUnlEr2/Z cUupobcAISbIJVJksw7HKLK7VqYQlMr6vE3jBJ20RzHAuy1MLw/7ceuVbLKqmhxAyV PC67csEuaWJqgAFbU/X1aHsHTbuB+oOwc+sETEUc= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:05 +0200 Subject: [PATCH v7 23/32] ipa: libipa: lsc: Make Components a map<> MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-23-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=17891; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=v4v4Eg4kSgf7KzA8URy4yQTBrx03pOq6Cpvt3Zh6qPA=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Ehs/nvt+Evx5gXcZMbBgRgbgZajHQOs9ly+ spR/rC2S+KJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIQAKCRByNAaPFqFW PDWiEACuDhhjYo4nADOserLRfA/TGp9Ix+U1zFJqOAXJjRa1MpArlad6F3OeOI/fagMnZG3WuS6 9aeF6dEbzjolEqCE2N9hKgpREHZBFQLv3EWnM/7SUAC1y5ne9Tlqy1XZW92xrtHGmcVysKZr9w+ qq2CaIlAVsYqWhkB2ZSJt2d32sLxATNn5gIIq7XkLwqsWfCY1NHHf0ZNEzJfO4QJStNKbmcZKiR e+Fyzo+uv0ZDSTIwjeJLrlAwZepGXUyXN99478Klfkirlhx/ACtNrFW5Wb7YDLguwiw/OItojUH iPix/KtBfQ7Avw5AFdI4IPfq/yiCVlM3bQSZ6ekB1Z3DZDkUh8b8c+hcyOh/h/+ZKODjdzjPD/1 eEDVZ3Vf5RLDOqpjsjaxN3ieYey6CwGSGkgeI1l5Kqj4qjWdh2V/c6sHmI9teVqP4oL62VXPWqw pNQV/ch86+xv2X1qYewKY1fMnCP3hPfp5zPVSg1P5zE2f7P4G55JdxnCRrjpZZCSGNjMaBzkYyv d09h1F0hpczFk5KGu8GO9NnjOUMy7dlkU1YtjHcCnzXL7lU/OiNLT6yic+YLxCm8o+v8sEl5Qky ftXpchCMUfb7MijahvW8Qmchg8G5oCBVebrDO+npdqFt+2ZW4dIGMJCACqa4X6y7HK3JZwTbtIs 8/F/KLeqC70nYpg== 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" The current definition of lsc::Components assumes 4 gain vectors named after the colour components supported by the RkISP1 ISP (r, gr, gb and b). As different ISP support different colour channels for their Lsc tables make lsc::Components a map that associates the name of the colour channel to the vector of gains. The use of strings as index for the map comes from the requirement of parsing the tuning file using string identifiers. Pass the strings to use for tuning file parsing to the LscAlgorithm::init() function as part of a newly introduced LscDescriptor type and use the same strings for indexing the gain arrays. The newly introduced LscDescriptor type also provides to the LscAlgorithm class the expected size of the Lsc grid, allowing to remove RkISP1 specific values from the libipa generic implementation. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- v5->v6: - Rename LscDescriptor::num*Cells to num*Samples --- src/ipa/libipa/lsc.cpp | 10 +++--- src/ipa/libipa/lsc.h | 4 +-- src/ipa/libipa/lsc_base.cpp | 49 ++++++++++++++++++++-------- src/ipa/libipa/lsc_base.h | 18 ++++++----- src/ipa/libipa/lsc_polynomial.cpp | 59 ++++++++++++++++----------------- src/ipa/libipa/lsc_polynomial.h | 10 ++---- src/ipa/libipa/lsc_table.cpp | 68 +++++++++++++++++++++++---------------- src/ipa/libipa/lsc_table.h | 9 ++++-- src/ipa/rkisp1/algorithms/lsc.cpp | 20 ++++++++---- 9 files changed, 144 insertions(+), 103 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index dd69c7a80b17..831f6b003df1 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -86,16 +86,16 @@ namespace lsc { /** * \param[in] tuningData The tuning data - * \param[in] sensorSize The physical sensor size * \param[in] controls The IPA list of supported controls + * \param[in] descriptor The LSC engine descriptor * * Parse \a tuningData according to the settings specified in \a descriptor to * populate the LSC data and registers LSC controls in \a controls. * * \return 0 on success, a negative error code otherwise */ -int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize, - ControlInfoMap::Map &controls) +int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &controls, + const LscDescriptor &descriptor) { polynomial_ = false; @@ -108,7 +108,7 @@ int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize, * \todo Most likely the reference frame should be native_size. * Let's wait how the internal discussions progress. */ - impl_ = std::make_unique(sensorSize); + impl_ = std::make_unique(descriptor.sensorSize); polynomial_ = true; LOG(Lsc, Debug) << "Using polynomial Lsc"; } else { @@ -123,7 +123,7 @@ int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize, return -EINVAL; } - int ret = impl_->parseLscData(yamlSets); + int ret = impl_->parseLscData(yamlSets, descriptor); if (ret) return ret; diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 9a51d32c3464..9fe8ad67ea33 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -38,8 +38,8 @@ struct FrameContext { class LscAlgorithm { public: - int init(const ValueNode &tuningData, const Size &sensorSize, - ControlInfoMap::Map &controls); + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls, + const LscDescriptor &descriptor); int configure(lsc::ActiveState &state, const Rectangle &analogCrop, const std::vector &xPos, diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index b3671376a12a..289cb3da33f2 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -19,23 +19,20 @@ namespace ipa { namespace lsc { /** - * \struct Components + * \typedef Components * \brief Associate colour components with a list of gains * * LSC tables are defined as a list of gain values associated to a colour * component. * - * \var Components::r - * \brief The list of gains for the Red colour component + * As different ISPs support different colour components (usually 'r', 'gr', + * 'gb', 'b' or just 'r', 'g', 'b') this class associates a string + * identifier for the colour component to a list of gains. * - * \var Components::gr - * \brief The list of gains for the Green/Red colour component + * Each key name shall match an entry in the tuning file. * - * \var Components::gb - * \brief The list of gains for the Green/Blue colour component - * - * \var Components::b - * \brief The list of gains for the Blue colour component + * The list of keys is provided to the LscAlgorithm class using \a + * LscDescriptor::keys. */ /** @@ -57,13 +54,32 @@ void Interpolator:: lsc::Components &dest, double lambda) { - interpolateVector(a.r, b.r, dest.r, lambda); - interpolateVector(a.gr, b.gr, dest.gr, lambda); - interpolateVector(a.gb, b.gb, dest.gb, lambda); - interpolateVector(a.b, b.b, dest.b, lambda); + for (auto const &[k, v] : a) + interpolateVector(v, b.at(k), dest[k], lambda); } #endif +/** + * \struct LscDescriptor + * \brief Describe the ISP LSC engine + * + * \var LscDescriptor::keys + * \brief The list of colour components to which a list of gains is associated + * with in the tuning file. Used for parsing the tuning file + * + * \var LscDescriptor::numHSamples + * \brief Number of horizontal gain samples of the ISP LSC grid. Used for + * validating the list of gains parsed from tuning file + * + * \var LscDescriptor::numVSamples + * \brief Number of vertical gain samples of the ISP LSC grid. Used for + * validating the list of gains parsed from tuning file + * + * \var LscDescriptor::sensorSize + * \brief The physical sensor size. This is the largest frame size used to + * generate the LSC table. Only used by the polynomial LSC algorithm + */ + /** * \class LscImplementation * \brief Pure virtual base class for LSC algorithm implementations @@ -80,6 +96,11 @@ void Interpolator:: * \fn LscImplementation::parseLscData * \brief Parse \a tuningData * \param[in] tuningData The tuning data + * \param[in] descriptor The LSC engine descriptor + * + * Parse the tuning file using the \a descriptor to identify the colour + * components in the tuning data and validate the size of the loaded gains + * tables. * * \return 0 on success, a negative error number otherwise */ diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 1bdb7d5c8ed9..4ad754b3727a 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -26,13 +26,7 @@ namespace ipa { namespace lsc { -struct Components { - std::vector r; - std::vector gr; - std::vector gb; - std::vector b; -}; - +using Components = std::map, std::less<>>; using ComponentsMap = std::map; } /* namespace lsc */ @@ -56,12 +50,20 @@ void Interpolator:: double lambda); #endif /* __DOXYGEN__ */ +struct LscDescriptor { + std::vector keys; + unsigned int numHSamples; + unsigned int numVSamples; + Size sensorSize; +}; + class LscImplementation { public: virtual ~LscImplementation() {} - virtual int parseLscData(const ValueNode &tuningData) = 0; + virtual int parseLscData(const ValueNode &tuningData, + const LscDescriptor &descriptor) = 0; virtual lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index 682e2f14fe09..c3871eb3295c 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -133,44 +133,42 @@ void Polynomial::setReferenceImageSize(const Size &size) /** * \brief Parse polynomial LSC data * \param[in] sets The tuning file content + * \param[in] descriptor The LSC engine descriptor * * Parse the LSC data in polyomial form from the \a sets tuning data. * * \return 0 on success or a negative error number otherwise */ -int LscPolynomial::parseLscData(const ValueNode &sets) +int LscPolynomial::parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) { for (const auto &set : sets.asList()) { - std::optional pr, pgr, pgb, pb; uint32_t ct = set["ct"].get(0); - if (lscData_.count(ct)) { - LOG(LscPolynomial, Error) - << "Multiple sets found for " - << "color temperature " << ct; - return -EINVAL; + PolynomialComponents components; + for (auto &k : descriptor.keys) { + auto polynomial = set[k].get(); + if (!polynomial) { + LOG(LscPolynomial, Error) + << "Missing polynomial for component " + << k; + return -EINVAL; + } + + auto [it, inserted] = + components.try_emplace(k, std::move(*polynomial)); + ASSERT(inserted); + + it->second.setReferenceImageSize(descriptor.sensorSize); } - pr = set["r"].get(); - pgr = set["gr"].get(); - pgb = set["gb"].get(); - pb = set["b"].get(); - - if (!(pr || pgr || pgb || pb)) { + auto [it, inserted] = lscData_.try_emplace(ct, std::move(components)); + if (!inserted) { LOG(LscPolynomial, Error) - << "Failed to parse polynomial for " - << "colour temperature " << ct; + << "Multiple sets found for " + << "color temperature " << ct; return -EINVAL; } - - pr->setReferenceImageSize(sensorSize_); - pgr->setReferenceImageSize(sensorSize_); - pgb->setReferenceImageSize(sensorSize_); - pb->setReferenceImageSize(sensorSize_); - - lscData_.emplace(std::piecewise_construct, - std::forward_as_tuple(ct), - std::forward_as_tuple(PolynomialComponents{ *pr, *pgr, *pgb, *pb })); } if (lscData_.empty()) { @@ -210,13 +208,12 @@ LscPolynomial::sampleForCrop(const Rectangle &cropRectangle, lsc::ComponentsMap components; - for (const auto &[k, p] : lscData_) { - components[k] = { - samplePolynomial(p.pr, xPos, yPos, cropRectangle), - samplePolynomial(p.pgr, xPos, yPos, cropRectangle), - samplePolynomial(p.pgb, xPos, yPos, cropRectangle), - samplePolynomial(p.pb, xPos, yPos, cropRectangle) - }; + for (const auto &[t, c] : lscData_) { + lsc::Components &comp = components[t]; + + for (const auto &[k, p] : c) + comp.try_emplace(k, samplePolynomial(p, xPos, yPos, + cropRectangle)); } return components; diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index d2276ecc6f6b..4c6af2d4900d 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -53,12 +53,7 @@ private: class LscPolynomial : public LscImplementation { private: - struct PolynomialComponents { - lsc::Polynomial pr; - lsc::Polynomial pgr; - lsc::Polynomial pgb; - lsc::Polynomial pb; - }; + using PolynomialComponents = std::map>; using PolynomialComponentsMap = std::map; public: @@ -67,7 +62,8 @@ public: { } - int parseLscData(const ValueNode &sets) override; + int parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) override; lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp index 47dff156963a..639f4f6aa8b0 100644 --- a/src/ipa/libipa/lsc_table.cpp +++ b/src/ipa/libipa/lsc_table.cpp @@ -7,9 +7,6 @@ #include "lsc_table.h" -/* \todo Remove RkISP1 from libipa. */ -#include "linux/rkisp1-config.h" - namespace libcamera { LOG_DEFINE_CATEGORY(LscTable) @@ -29,42 +26,56 @@ namespace ipa { /** * \brief Parse tabular LSC data * \param[in] sets The tuning file content + * \param[in] descriptor The LSC engine descriptor * * Parse the LSC data in tabular form from the \a sets tuning data. * * \return 0 on success or a negative error number otherwise */ -int LscTable::parseLscData(const ValueNode &sets) +int LscTable::parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) { for (const auto &set : sets.asList()) { uint32_t ct = set["ct"].get(0); - if (lscData_.count(ct)) { - LOG(LscTable, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } + int ret = parseLscComponent(set, ct, descriptor); + if (ret) + return ret; + } - lsc::Components components; - components.r = parseTable(set, "r"); - components.gr = parseTable(set, "gr"); - components.gb = parseTable(set, "gb"); - components.b = parseTable(set, "b"); + if (lscData_.empty()) { + LOG(LscTable, Error) << "Failed to load any sets"; + return -EINVAL; + } - if (components.r.empty() || components.gr.empty() || - components.gb.empty() || components.b.empty()) { + return 0; +} + +int LscTable::parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor) +{ + lsc::Components component; + for (auto &k : descriptor.keys) { + auto [it, inserted] = + component.try_emplace(k, parseTable(yamlSet, + k.c_str(), + descriptor.numHSamples, + descriptor.numVSamples)); + ASSERT(inserted); + + if (it->second.empty()) { LOG(LscTable, Error) - << "Set for color temperature " << ct - << " is missing tables"; + << "Set " << k << " for color temperature " + << ct << " is missing"; return -EINVAL; } - - lscData_.emplace(ct, std::move(components)); } - if (lscData_.empty()) { - LOG(LscTable, Error) << "Failed to load any sets"; + auto [it, inserted] = lscData_.try_emplace(ct, std::move(component)); + if (!inserted) { + LOG(LscTable, Error) + << "Multiple sets found for color temperature " + << ct; return -EINVAL; } @@ -72,17 +83,18 @@ int LscTable::parseLscData(const ValueNode &sets) } std::vector LscTable::parseTable(const ValueNode &tuningData, - const char *prop) + const char *prop, + unsigned int numHSamples, + unsigned int numVSamples) { - static constexpr unsigned int kLscNumSamples = - RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + unsigned int lscNumSamples = numHSamples * numVSamples; std::vector table = tuningData[prop].get>().value_or(utils::defopt); - if (table.size() != kLscNumSamples) { + if (table.size() != lscNumSamples) { LOG(LscTable, Error) << "Invalid '" << prop << "' values: expected " - << kLscNumSamples + << lscNumSamples << " elements, got " << table.size(); return {}; } diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index 67c7464ea5b6..a33208761c15 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -26,7 +26,8 @@ namespace ipa { class LscTable : public LscImplementation { public: - int parseLscData(const ValueNode &sets) override; + int parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) override; lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, @@ -39,8 +40,12 @@ public: } private: + int parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor); std::vector parseTable(const ValueNode &tuningData, - const char *prop); + const char *prop, + unsigned int numHSamples, + unsigned int numVSamples); lsc::ComponentsMap lscData_; }; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index b0d6649be4fd..7a95bfcfb93a 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -123,8 +123,12 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, xPos_ = sizesListToPositions(xSize_); yPos_ = sizesListToPositions(ySize_); - return lscAlgo_.init(tuningData, context.sensorInfo.activeAreaSize, - context.ctrlMap); + return lscAlgo_.init(tuningData, context.ctrlMap, { + .keys = { "r", "gr", "gb", "b" }, + .numHSamples = RKISP1_CIF_ISP_LSC_SAMPLES_MAX, + .numVSamples = RKISP1_CIF_ISP_LSC_SAMPLES_MAX, + .sensorSize = context.sensorInfo.activeAreaSize + }); } /** @@ -173,10 +177,14 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set) { - std::copy(set.r.begin(), set.r.end(), &config.r_data_tbl[0][0]); - std::copy(set.gr.begin(), set.gr.end(), &config.gr_data_tbl[0][0]); - std::copy(set.gb.begin(), set.gb.end(), &config.gb_data_tbl[0][0]); - std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]); + const auto &r = set.at("r"); + std::copy(r.begin(), r.end(), &config.r_data_tbl[0][0]); + const auto &gr = set.at("gr"); + std::copy(gr.begin(), gr.end(), &config.gr_data_tbl[0][0]); + const auto &gb = set.at("gb"); + std::copy(gb.begin(), gb.end(), &config.gb_data_tbl[0][0]); + const auto &b = set.at("b"); + std::copy(b.begin(), b.end(), &config.b_data_tbl[0][0]); } /** From patchwork Wed Aug 5 16:13:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27647 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 8C304C3348 for ; Wed, 5 Aug 2026 16:13:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1F7F56818D; Wed, 5 Aug 2026 18:13:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="IiPKhfT3"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 62FBF68164 for ; Wed, 5 Aug 2026 18:13:40 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 63A912B5C; Wed, 5 Aug 2026 18:12:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946349; bh=EIoRTYcRxBXUh/gCEob8/0V/PQOEtmC11Bd6tGcGu5A=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=IiPKhfT3jI+4+Mv4WSc9Ws0wquMVFZKaV9F4QwcsU04pEy4aCOAN7H3cUpa2udXpU bE1MqJ/Oi/lXjJRKRIekP7L+qln7CJKQ4dLtu6PbPUXScTNhie4pq1elEI3hKcuhWX 7N9TW8TyB5Z9LlCGhjWBLAGgOTNrqtGMBZLJ4tU8= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:06 +0200 Subject: [PATCH v7 24/32] ipa: libipa: lsc: Document Components parsing MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-24-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=4373; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=EIoRTYcRxBXUh/gCEob8/0V/PQOEtmC11Bd6tGcGu5A=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EhMH6SJkKBp+o6RNsehjgtMjuOVy2o0ZigK 9CKJN6GYRuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIQAKCRByNAaPFqFW PIh7EACUOuy6eIBfsRNjBCEm4q0/qJ1Z0PxsHS7n+08ypmUn0vXsH3f2+0mNBTNaEY/HbzJe2xI bid69DMRDH8TAMFpE8W6l1w2x5N0kVZ1aZ9UfeA8u/iqPRMcbLP30UaPDGRFMR8QVOlxxvkVs3Y 31rUnaT9biPkpcFMJHFFrWIVXj8LHJdLhziQndgWdPmbPX6SbE8N6HsJ6X6gcenwCf7ln9D899Q Dv3QWQJJpXX6ujmtFtV9JeMIPHnmtyt3Zm4ctx59tGhVusTwLSOToPglhHEuWEjXOQL+0vOMfl7 N09NRapgjsYsi2Z2ppoHgbaSpA45I7Np3k8qHMQku1KcvSMYJlTFaqRT7SvAtmuozPr/rjPzkIu U8kBrKSmqQbY9WXx+S/PHU4B/SXCPu/QxCwzc4Pf8Pg5x4X5UtgQF9DEVAjXiUBiNxURZb1GdHA Le/Bj3YgAX5BmAaymqMJuTiy6vumh4YJkW5XVvKuzKS0mPpRqiLpg/P/gEOVh2wboprhi6DEFSa wR3TdwhiXuGNgZPOfjtzFAeH1oNnsLRxeB/neLGt7unJDygAhAcI/ntdWtzUFF+UOxPiPa4eYEv WJHAN2UpJhR5fFCFnSVX6WxMo4E1Ng09hYpkIIvJ6MPTliJ5U1E6+5od+Y+YK4sJB6QfE9cRCu0 xSTdFXrwUny9u9w== 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" Document with examples how different IPA modules parse the tuning file depending on the number of colour channels they support. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc.cpp | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 831f6b003df1..32626049a2e2 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -68,6 +68,117 @@ namespace lsc { * tables are indexed by colour temperature with per-colour-components vectors * of gain values or polynomial coefficients. * + * The colour components names are IPA-implementation specific and depend on the + * ISP LSC engine design. Some LSC engine support 4 colour components (r, gr, + * gb, b), some only support 3 colour components (r, g, b). The name (and + * number) of the expected colour components shall be provided to + * LscAlgorithm::init() using the LscDescriptor::keys field. + * + * Example of a tabular lens shading tuning file with 'r', 'g' and 'b' colour + * components. The gain table has been omitted here, but the expected number + * of entries has to be equal to + * LscDescriptor::numHSamples * LscDescriptor::numVSamples. + * + * \code{.yaml} + * - Lsc: + * sets: + * - ct: 2500 + * r: [ + * .. gains table omitted.. + * ] + * g: [ + * .. gains table omitted.. + * ] + * b: [ + * .. gains table omitted.. + * ] + * - ct: 6500 + * r: [ + * .. gains table omitted.. + * ] + * g: [ + * .. gains table omitted.. + * ] + * b: [ + * .. gains table omitted.. + * ] + * \endcode + * + * Example of a polynomial lens shading tuning file with 'r', 'gr', 'gb' and 'b' + * colour components: + * + * \code{.yaml} + * - Lsc: + * type: "polynomial" + * sets: + * - ct: 2500 + * r: + * cx: 0.5006571711950275 + * cy: 0.510093737499277 + * k0: 1.5393282208428813 + * k1: -1.1434559757908016 + * k2: 4.332602305814554 + * k3: 0.0 + * k4: 0.0 + * gr: + * cx: 0.5009320529087338 + * cy: 0.511208038949085 + * k0: 1.5634738574805407 + * k1: -1.5623484259968348 + * k2: 4.846686073656501 + * k3: 0.0 + * k4: 0.0 + * gb: + * cx: 0.5012013290343839 + * cy: 0.5128251541578288 + * k0: 1.526147944919103 + * k1: -1.4316976083689723 + * k2: 4.792604063222728 + * k3: 0.0 + * k4: 0.0 + * b: + * cx: 0.49864139511067784 + * cy: 0.5162095081739346 + * k0: 1.0405245474038738 + * k1: 0.05618339879447103 + * k2: 1.8792813594001752 + * k3: 0.0 + * k4: 0.0 + * - ct: 6000 + * r: + * cx: 0.5006202239353942 + * cy: 0.5099531318307661 + * k0: 1.4702946023945032 + * k1: -0.8893767547927631 + * k2: 3.920547732201387 + * k3: 0.0 + * k4: 0.0 + * gr: + * cx: 0.500907874178317 + * cy: 0.511084916024106 + * k0: 1.5336172760559457 + * k1: -1.39964026514435 + * k2: 4.565487728954618 + * k3: 0.0 + * k4: 0.0 + * gb: + * cx: 0.5011898608900477 + * cy: 0.5126797906745105 + * k0: 1.5013145790354843 + * k1: -1.2747407173754124 + * k2: 4.514682876897286 + * k3: 0.0 + * k4: 0.0 + * b: + * cx: 0.4987561413116136 + * cy: 0.5159619420778772 + * k0: 1.0102986422191802 + * k1: 0.13263449763985727 + * k2: 1.686556107316064 + * k3: 0.0 + * k4: 0.0 + * \endcode + * * At LscAlgorithm::configure() time the LSC tables are re-sampled on the * sensor's crop rectangle in use to adapt them to the configuration in use for * a streaming session. Polynomial LSC tables support re-sampling and can be From patchwork Wed Aug 5 16:13:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27648 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 385C8C3349 for ; Wed, 5 Aug 2026 16:13:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B95D368176; Wed, 5 Aug 2026 18:13:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gpXU2o7a"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 024146815D for ; Wed, 5 Aug 2026 18:13:41 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 32CC03A1C; Wed, 5 Aug 2026 18:12:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946350; bh=M+fhDFMVeC56oP6o4RBljPBP/g+PMAsqSnprrwRtIGE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=gpXU2o7aUavh40D6VC91z5x08ekzih/zJ6LKuNZft4aH9qMAJZG/fk9ZMCy/JDUFK ZR/wd+tDBa03SOpJmxQSzuiAbujhYOxzMFjoNh5taJN/05zRZERstC3qUVNMKu4hr/ f2w16vvCudTZu4KwiYzqT2qvBLFzUr56zry3IHhU= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:07 +0200 Subject: [PATCH v7 25/32] ipa: libipa: lsc_polynomial: Remove sensorSize_ MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-25-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=3310; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=M+fhDFMVeC56oP6o4RBljPBP/g+PMAsqSnprrwRtIGE=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EhLF0PSNsubEXvKKaAggussVkC7HraJm/Mu GA67W2uDJSJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIQAKCRByNAaPFqFW PFwJD/42edBnsf/JeLciDXrYV7PlgsJk6rMQpQAdDQyDCATBxj7uxNBoLpUdVE08cHcpxD5i037 eEG5G8V06coibazmQFwkAGh3Ae6TonG0Awp71eDujkzzwYTryZr//g+DMwIY2nVdr2moHGRMNH1 j0LgmUXoRapS4MIn0MJM/vy6ob0C3GgReDd8i6qyWcJlyvg0tJQ6I2Z8o0vzuEcVDkpdacF+tJU YXjnDr2W3zN+N9Wi6/SC6CzG3gC0EhVQR/m/hUJVvEIJnNWpzn8BP6WZTMcb/RZRViSfEVs2pEO n5bUC9Ksj62Jgb8qu98ToC9amG4qddK/iJ6wH1E4L8qtIL8gphnB7DRZnvfo3aO+vaImTqhI3DH 7xvAS8fgbSM/H5AHtX88l33cExhcsJg3tPQcHOJFZNvJPxRIkwMrJ9mulfDDhu4aqfRcWSnFZfb OlP8KrFOiB9/5r1PGhR8dHasymSfro4PUR/FaXGED8joFFwyznopWDbYSEY/yLmyD/1IRuI+gZ/ jYM9ggpECKzMu0DxpwiZtdRk/CBQ0n1rGgBbGqvlLaxoleZR4GLXpxUWNAvmY+6oCc2Y/MxSb+y vGRh5zWLAF7ihGcvKCBdB1L8WvmOD2U4o07r/GY8wB2lPRfcDNtGB1hLK4yZWI35ZuWSLMESZN9 VKPFg61aAmcx4rg== 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" The sensorSize_ field is only used during the init() function and is provided to LscPolynomial using the LscDescriptor type. There is no need to store it as a class member and to pass it in at class construction time. Remove it and move the \todo comment in the LscDescriptor type documentation. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Stefan Klug --- src/ipa/libipa/lsc.cpp | 6 +----- src/ipa/libipa/lsc_base.cpp | 3 +++ src/ipa/libipa/lsc_polynomial.cpp | 7 ------- src/ipa/libipa/lsc_polynomial.h | 6 ------ 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 32626049a2e2..2062b795991d 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -215,11 +215,7 @@ int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &control impl_ = std::make_unique(); LOG(Lsc, Debug) << "Using table-based Lsc"; } else if (type == "polynomial") { - /* - * \todo Most likely the reference frame should be native_size. - * Let's wait how the internal discussions progress. - */ - impl_ = std::make_unique(descriptor.sensorSize); + impl_ = std::make_unique(); polynomial_ = true; LOG(Lsc, Debug) << "Using polynomial Lsc"; } else { diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 289cb3da33f2..cb770f164d53 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -78,6 +78,9 @@ void Interpolator:: * \var LscDescriptor::sensorSize * \brief The physical sensor size. This is the largest frame size used to * generate the LSC table. Only used by the polynomial LSC algorithm + * + * \todo: Most likely the reference frame should be native_size. + * Let's wait how the internal discussions progress. */ /** diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index c3871eb3295c..0eb017de6b50 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -123,13 +123,6 @@ void Polynomial::setReferenceImageSize(const Size &size) * \sa LscImplementation */ -/** - * \fn LscPolynomial::LscPolynomial - * \param[in] sensorSize The physical sensor size - * - * Construct an LscPolynomial - */ - /** * \brief Parse polynomial LSC data * \param[in] sets The tuning file content diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 4c6af2d4900d..548ed94b2461 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -57,11 +57,6 @@ private: using PolynomialComponentsMap = std::map; public: - LscPolynomial(const Size &sensorSize) - : sensorSize_(sensorSize) - { - } - int parseLscData(const ValueNode &sets, const LscDescriptor &descriptor) override; @@ -75,7 +70,6 @@ private: Span yPositions, const Rectangle &cropRectangle); PolynomialComponentsMap lscData_; - Size sensorSize_; }; } /* namespace ipa */ From patchwork Wed Aug 5 16:13:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27650 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 5B47EC334B for ; Wed, 5 Aug 2026 16:14:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D2CBE68183; Wed, 5 Aug 2026 18:13:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Eh6QvPyM"; 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 AFD6068161 for ; Wed, 5 Aug 2026 18:13:41 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CD1123A1D; Wed, 5 Aug 2026 18:12:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946351; bh=6Obv0Wz/HJF7mOUnS6V31ONAzqtsTuCPOBefw8+/xCs=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Eh6QvPyMkOnfIlnQqUolZyxt02MTqqPZufNLSQLm8GfylsNzuyLj7gpOaSU0LT9Ad W6++nsbMsUnxiBxyesDX1NBpzVxfteucWbEMWwU7SK1fBaMOLYXUdPg7bLNSuD0HvZ pu8Px1+g72hYNrfh1kMvTK90imQp5cNAfkNC8NPs= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:08 +0200 Subject: [PATCH v7 26/32] ipa: libipa: lsc: Document LscDescriptor::sensorSize usage MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-26-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=1079; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=6Obv0Wz/HJF7mOUnS6V31ONAzqtsTuCPOBefw8+/xCs=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EiZ2qfeESd84cm3e4fP+NfLd3gg+QTUYBtE bfMqDSzEMKJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIgAKCRByNAaPFqFW PG2CEACqR8F1X6FLhSc/w/30KKxx/fmIDn9DA0KhpZVDCC2vb9wKgvhJx1GmWKA9Xm1KYbnG5OM HtvA33Nf7+J/owMzXiecZgpr77/8J338UUkNM0HQC2o9+OlROi+f0XKQRoTEhKVdWwY86cEU9hi PxenoiO6gMxx9YuVy1Rjm7/9qsU94tuizGmzwQjSig2o8JK4W7eS1uJagy1Nu8ujZN5jQK/segS LjobgJ+mpUzDlEw2cDhFZ1PfXh7l0okIOL6qC8HY0Ey2a8l0TfXeJO1VZLqSdEzHcwakA6Cy5ej rCvnOBVYgYoz9j4wtH6YUITgx3fEIGNV0OYipy7bTD+6j1tYWAOY1KarS5KIvvJeK6VQwPzD0ym ZWC9vlNZ+4/Rj2EU6fviPPNzXqCPFO1fSMjpP6hEtnVXYyrl3grTnuq+P4LrfWBOE5TY2J4JT2u Y0JY7xZDuHs33CbGIYSKbq88KgWnTN6AHGALkmnGGf9UTXNksifz8ko+F8pIm/nCXNg21atzM8+ XeBCxp8XzFnnqZYd2d2hsSfBqwQUkIRIVZ++Dly7OjU9SxVMMh+FsuaseH4Uwfoc6xOVpHX4cot uo9qi9p+jnuRGjTiECUNDnW6cyS2wb5jhksqxMMDqIn5Fts94aPSzemyHEbwMHQzjlMMQcX41ZQ RqeYQaQpkgKgOeQ== 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" Now that sensorSize_ has been removed from LscPolynomial and is passed to the Lsc algorithm through LscDescriptor document its intended purpose. Signed-off-by: Jacopo Mondi Reviewed-by: Stefan Klug Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 2062b795991d..8c5c28077cce 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -179,6 +179,10 @@ namespace lsc { * k4: 0.0 * \endcode * + * The LSC polynomial definition is generated at tuning time in relation + * to the full sensor pixel array. This needs to be specified in + * LscDescriptor::sensorSize. + * * At LscAlgorithm::configure() time the LSC tables are re-sampled on the * sensor's crop rectangle in use to adapt them to the configuration in use for * a streaming session. Polynomial LSC tables support re-sampling and can be From patchwork Wed Aug 5 16:13:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27652 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 87C5CC334D for ; Wed, 5 Aug 2026 16:14:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 140706816D; Wed, 5 Aug 2026 18:14:01 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KYDKyGBA"; 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 4C61E68163 for ; Wed, 5 Aug 2026 18:13:42 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 88C4B981; Wed, 5 Aug 2026 18:12:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946351; bh=vDonS8TQg8+qGGtDqSm4kxg7JZ/OvVkZjmA0BUQhIOs=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=KYDKyGBAl70pEvS31HZBJmCtNqL0sRubx3GR/VDK5p+zzvCvW5W2qnZzp0Wr1mWFJ XPgkCPZaDdD1MUKdg6qpwy/aQbwyIyVrXG7+KKmI3T5SDJbJ5jkm+LGKoxlqYJSUWZ ErtR6nwbVIYQ0P9WBA0h3SlNQ6UeqalhGsBqcqws= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:09 +0200 Subject: [PATCH v7 27/32] ipa: libipa: lsc: Quantize gains in IPA MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-27-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=7176; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=vDonS8TQg8+qGGtDqSm4kxg7JZ/OvVkZjmA0BUQhIOs=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EiZ1l3BVAans4LNGssd6KgWb1lspup9Sv3j g2lhWWXZ0GJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIgAKCRByNAaPFqFW PAKvD/9f+X0r1VKpnuXVzfWJY5Ubgr4G5Ez1rKKOalN8SJgxL0ZmCPwYHAKTKsoL4/yUQdWkmRE Nx2TJMTCM/nHmK9GYpOowQMLt6dh8DiEMoDR3X8lsjlyrSui11J8dTuKSkZaZ8uumQ0mjXMMefJ 8CTT5Tw4e6m4gHR+w4FpVX65VSpl3oIUUChZzPLbyqRwX7UsyFFEQIQXtBVo3WvNRGOL/fIbHUF AsO6ZCCqyPXm6j92cPEGvJbRXKoUbWj34YjjKq9902qmvg2GuiKXVcfoWZKmlPMf8f70r6ZzUR3 CbztqLBnsm9vlCMDogSy1W/PwdzHUod5x11T+SVikcIH4YcpU0sX05WfGerdCpw1DEh1uU1PlGy 7rIfG/a7CZg9XW1iTJjFl4ZccvcyNlsQzL/b1L7a+GOCd+iokB8unA6STv579K3vrWeZqPSxLsS m2O+41YbBo6jwsJ1+QRlz+Gdu/e+3rFRhDAH7FjahqthDw6vLBFLIMZs3Iz/u7w4NMoEkNtKrEB AAzqjgntsajGzOnZvFx63kLDbX750gyHAHZhWsjuagNmXTi16RZsg5j+LUo37PMoXUjWB3vRuFM /WcyQdrxtYE7lKw4pG7f92xZQFR8DqMnrGBZjiSNvIX3qMMTMOKDaVqjX7zw9gayf+ig4NMpqeg zRBDfamU+1fSisQ== 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" The libIPA Lsc algorithm currently assumes the register format of the RkISP1 platform (16 bits Q2.10 format), as that's where the implementation has been derived from. Make lsc::Components use floats as the default exchange type between the LscAlgorithm and the IPA module and move the gains quantization to the plaltform IPA module. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc_base.h | 2 +- src/ipa/libipa/lsc_polynomial.cpp | 17 +++++------------ src/ipa/libipa/lsc_polynomial.h | 8 ++++---- src/ipa/libipa/lsc_table.cpp | 22 ++++++++++++++++------ src/ipa/libipa/lsc_table.h | 2 +- src/ipa/rkisp1/algorithms/lsc.cpp | 23 +++++++++++++++-------- 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 4ad754b3727a..2cff34b32a2b 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -26,7 +26,7 @@ namespace ipa { namespace lsc { -using Components = std::map, std::less<>>; +using Components = std::map, std::less<>>; using ComponentsMap = std::map; } /* namespace lsc */ diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index 0eb017de6b50..419de2c12cf0 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -212,7 +212,7 @@ LscPolynomial::sampleForCrop(const Rectangle &cropRectangle, return components; } -std::vector +std::vector LscPolynomial::samplePolynomial(const lsc::Polynomial &poly, Span xPositions, Span yPositions, @@ -223,7 +223,7 @@ LscPolynomial::samplePolynomial(const lsc::Polynomial &poly, double y0 = cropRectangle.y / m; double w = cropRectangle.width / m; double h = cropRectangle.height / m; - std::vector samples; + std::vector samples; samples.reserve(xPositions.size() * yPositions.size()); @@ -231,16 +231,9 @@ LscPolynomial::samplePolynomial(const lsc::Polynomial &poly, for (double x : xPositions) { double xp = x0 + x * w; double yp = y0 + y * h; - /* - * The hardware uses 2.10 fixed point format and limits - * the legal values to [1..3.999]. Scale and clamp the - * sampled value accordingly. - */ - int v = static_cast( - poly.sampleAtNormalizedPixelPos(xp, yp) * - 1024); - v = std::clamp(v, 1024, 4095); - samples.push_back(v); + + samples.push_back(static_cast + (poly.sampleAtNormalizedPixelPos(xp, yp))); } } return samples; diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 548ed94b2461..24b216a1aa25 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -65,10 +65,10 @@ public: std::vector xPos, std::vector yPos) override; private: - std::vector samplePolynomial(const lsc::Polynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle); + std::vector samplePolynomial(const lsc::Polynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle); PolynomialComponentsMap lscData_; }; diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp index 639f4f6aa8b0..3e8b1a060ce7 100644 --- a/src/ipa/libipa/lsc_table.cpp +++ b/src/ipa/libipa/lsc_table.cpp @@ -30,6 +30,11 @@ namespace ipa { * * Parse the LSC data in tabular form from the \a sets tuning data. * + * \todo Currently the gain values parsed from tuning file are expressed in the + * platform fixed-point register format. Use gains in floating point + * representation for tabular LSC data in order to facilitate re-use of LSC + * tuning data across different platforms. + * * \return 0 on success or a negative error number otherwise */ int LscTable::parseLscData(const ValueNode &sets, @@ -82,15 +87,20 @@ int LscTable::parseLscComponent(const ValueNode &yamlSet, return 0; } -std::vector LscTable::parseTable(const ValueNode &tuningData, - const char *prop, - unsigned int numHSamples, - unsigned int numVSamples) +std::vector LscTable::parseTable(const ValueNode &tuningData, + const char *prop, + unsigned int numHSamples, + unsigned int numVSamples) { unsigned int lscNumSamples = numHSamples * numVSamples; - std::vector table = - tuningData[prop].get>().value_or(utils::defopt); + /* + * Cast to float even if gains are expressed as fixed-point + * representations. This prepares to express gains in floating point + * formats in tuning files. + */ + std::vector table = + tuningData[prop].get>().value_or(utils::defopt); if (table.size() != lscNumSamples) { LOG(LscTable, Error) << "Invalid '" << prop << "' values: expected " diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index a33208761c15..6d9b0c692b7f 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -42,7 +42,7 @@ public: private: int parseLscComponent(const ValueNode &yamlSet, unsigned int ct, const LscDescriptor &descriptor); - std::vector parseTable(const ValueNode &tuningData, + std::vector parseTable(const ValueNode &tuningData, const char *prop, unsigned int numHSamples, unsigned int numVSamples); diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 7a95bfcfb93a..911ac6895a14 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -177,14 +177,21 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set) { - const auto &r = set.at("r"); - std::copy(r.begin(), r.end(), &config.r_data_tbl[0][0]); - const auto &gr = set.at("gr"); - std::copy(gr.begin(), gr.end(), &config.gr_data_tbl[0][0]); - const auto &gb = set.at("gb"); - std::copy(gb.begin(), gb.end(), &config.gb_data_tbl[0][0]); - const auto &b = set.at("b"); - std::copy(b.begin(), b.end(), &config.b_data_tbl[0][0]); + /* + * The hardware uses 2.10 fixed point format and limits the legal values + * to [1..3.999]. Scale and clamp the sampled values accordingly. + */ + const auto quantizeSet = [&](const std::string &key, uint16_t *dst) { + const auto &s = set.at(key); + std::transform(s.begin(), s.end(), dst, [](float f) { + return std::clamp(f * 1024, 1024, 4095); + }); + }; + + quantizeSet("r", &config.r_data_tbl[0][0]); + quantizeSet("gr", &config.gr_data_tbl[0][0]); + quantizeSet("gb", &config.gb_data_tbl[0][0]); + quantizeSet("b", &config.b_data_tbl[0][0]); } /** From patchwork Wed Aug 5 16:13:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27651 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 ED4AFC334C for ; Wed, 5 Aug 2026 16:14:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 704BB68186; Wed, 5 Aug 2026 18:14:00 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Y5U6sqLk"; 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 D25066816D for ; Wed, 5 Aug 2026 18:13:42 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1D4B13A20; Wed, 5 Aug 2026 18:12:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946352; bh=0icdnYN3wJsMUHWBqdqovgOrFElVBnBhToX1Em0XrvE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Y5U6sqLkzVQiPDp/YFqKESF2pv1w1VFVt2/tYNy60JzAIhwslptoySUCEHOc/8KNu G95x1eJ3dk9iMALtkNepdg6XShQ+bg7DvutZHtdJs8928S25nrBXLEY9kd2lcrUQKd J31lMIPLB7qsPVDD0lmTp9RGJ9jzEkRkuLw8TJjM= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:10 +0200 Subject: [PATCH v7 28/32] ipa: libipa: lsc: Quantize gains in LscAlgorithm MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-28-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=22720; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=0icdnYN3wJsMUHWBqdqovgOrFElVBnBhToX1Em0XrvE=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Eiz0WxptMA3R8PH+5xCNItHtLe1QDsE6h4J BbiOkx3TwmJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIgAKCRByNAaPFqFW PGKyEADDLVscLnW2s4txMV3SEBCSSG0jo/SENjppPDym/sMwwr3/SokOupYcir+G4lElM260Mjn 7RRFrKeBqxE853PdkqDUyUelFg+zE/U1blpOsG4fiss7OjhFt0mooFFRR6j0CaTiGDGEVkpnKH8 rG5h7VRX/mxt2uxEGUUFWYsg/uP3xJNivBqQc/5pQkYcq8Qk4/zTk4dVNA/I6Vl8kNLpruPWL8L wDD4twq5qLznrcKtg80OLdPQKBzWc7SNKMR4C6z+bejSY/y65IuJ0hjAk6eQdpjCk9gJRHry0yf a9Otv9CVGxmrssGw9djQvJmEoo4bpqkhp5pb13grQi0gcL4AYKXZbsVAD9JzchubNqD0Socv4Mh EZv4riRLHo38TusStYKjbS52b2DCZ+BnzDMNC4Vbzq8ywx3PfYLWS8WcsQFtP4B1cv2e6qDc4cC xrz0U9B4jmMnKewGamyo6AiWS1FpIZHpaT9VCBKV3kGxb5CTOSD8DO69GKEOfI41l4GIvIOcgii upapCJUXot1x3iwrpQZUJwT1TeUCqb73ec5eifyEHQ3qcPUttHlSleJb/+hrtNckLM2AHKebg5q Hknr4EjQZ//NEpHuHpWyFEL7nId2jaLq2AjBno9F9BLbg7HnV2+Fe545iNqcJsUeofrsu3sYg0W KX/dZP7CMYdIhfw== 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" Template the LscAlgorithm class with the LSC engine register format and make lsc::Components<> parameteric on the type used to store the quantized LSC gains. This allows to quantize gains in the LscAlgorithm class and not in the IPA modules. Move the Components<> and ComponentsMap<> type to lsc.h in order for IPA modules to be able to access the quantized gains, and make the existing float-based Components internal to LscImplementation to be used as the exchange format between LscImplementation and LscAlgorithm. Split the LscAlgorithm class in two, one LscAlgorithmBase one for non-templated function implementations and one for the templated configure() implementation and for templated data members and accessor. Note that currently table-based LSC data do not require quantization as they're already expressed in register format in tuning files. This will change soon to make it easier to re-use LSC data between different platforms by expressing gains in floating point format in tuning files as well. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc.cpp | 134 +++++++++++++++++++++++++++++--------- src/ipa/libipa/lsc.h | 83 +++++++++++++++++++---- src/ipa/libipa/lsc_base.cpp | 75 +++++++++------------ src/ipa/libipa/lsc_base.h | 31 ++------- src/ipa/libipa/lsc_polynomial.cpp | 6 +- src/ipa/libipa/lsc_polynomial.h | 2 +- src/ipa/libipa/lsc_table.cpp | 2 +- src/ipa/libipa/lsc_table.h | 4 +- src/ipa/rkisp1/algorithms/lsc.cpp | 28 +++----- src/ipa/rkisp1/algorithms/lsc.h | 6 +- 10 files changed, 231 insertions(+), 140 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 8c5c28077cce..bb02290fc9ad 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -46,11 +46,91 @@ namespace lsc { * \brief Boolean flag for the LscAlgorithm updated status */ +/** + * \typedef Components + * \brief Associate colour components with a list of gains in register format + * \tparam T The type used to store the gain values + * + * LSC tables are defined as a list of gain values associated to a colour + * component. + * + * As different ISPs support different colour components (usually 'r', 'gr', + * 'gb', 'b' or just 'r', 'g', 'b') this class associates a string + * identifier for the colour component to a list of gains. + * + * Each key name shall match an entry in the tuning file. + * + * The list of keys is provided to the LscAlgorithm class using \a + * LscDescriptor::keys. + * + * The gain values are expressed in fixed-point register format stored in + * variabled of type \a T, as each platform potentially has a different register + * width. + */ + +/** + * \typedef ComponentsMap + * \brief Associate a colour temperature to a LSC table + * \tparam T The type of the gain values + * + * An LSC table is generated during the tuning phase for a specific colour + * temperature, and a tuning file usually contains LSC tables generated for + * several different colour temperatures. + */ + } /* namespace lsc */ +#ifndef __DOXYGEN__ +template +void interpolateVector(const std::vector &a, + const std::vector &b, + std::vector &dest, double lambda) +{ + ASSERT(a.size() == b.size()); + dest.resize(a.size()); + for (size_t i = 0; i < a.size(); i++) + dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda; +} + +template<> +void Interpolator>:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, + double lambda) +{ + for (auto const &[k, v] : a) + interpolateVector(v, b.at(k), dest[k], lambda); +} +#endif + +/** + * \class LscAlgorithmBase + * \brief Base class for LscAlgorithm + * + * Base class for LscAlgorithm for non-templated functions implementation + */ + +/** + * \var LscAlgorithmBase::impl_ + * \brief The LSC algorithm implementation + * + * There are two classes derived from LscImplementation, the LscTable and + * LscPolynomial ones. Which one to instantiate is decided by parsing the tuning + * file. + */ + +/** + * \var LscAlgorithmBase::polynomial_ + * \brief Boolean flag for polynomial LSC + * + * Set to true if polynomial LSC is in use. + */ + /** * \class LscAlgorithm * \brief libIPA LSC algorithm implementation + * \tparam U The platform fixed-point register format representation * * Due to the optical characteristics of the lens, the light intensity received * by the sensor is not uniform. The Lens Shading Correction algorithm applies @@ -192,6 +272,14 @@ namespace lsc { * * \todo Implement grid based re-sampling * + * After re-sampling, the LSC tables gain values are convereted from their + * floating point representation (LscImplementation::Components) to the + * platform's register representation (lsc::Components<>). Grid-based LSC tables + * currently already contain gains represented in register format, so no + * quantization is necessary but only a simple cast is required. + * + * \todo Express gains in floating point format for grid-based LSC tables + * * When the IPA algorithms wants to get access to the (re-sampled) tables to * program its LSC engine, it uses LscAlgorithm::interpolateComponents() to get * an LSC table interpolated by the LscAlgorithm class for the specified colour @@ -209,8 +297,8 @@ namespace lsc { * * \return 0 on success, a negative error code otherwise */ -int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &controls, - const LscDescriptor &descriptor) +int LscAlgorithmBase::init(const ValueNode &tuningData, ControlInfoMap::Map &controls, + const LscDescriptor &descriptor) { polynomial_ = false; @@ -245,12 +333,15 @@ int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &control } /** + * \fn LscAlgorithm::configure() + * \brief Re-sample and quantize LSC data * \param[in] state The LSC active state * \param[in] analogCrop The current sensor analog crop rectangle * \param[in] xPos List of horizontal positions of the LSC grid nodes * \param[in] yPos List of vertical positions of the LSC grid nodes * - * Re-sample the LSC data for an \a analogCrop. + * Re-sample the LSC data for an \a analogCrop and convert gains to their + * register representation using the class template paramter \a U. * * LSC tables are generated at tuning time using a known sensor configuration. * When a new streaming session is started, it might use a different sensor @@ -263,30 +354,12 @@ int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &control * * \sa LscImplementation::sampleForCrop * + * Once tables have been re-sampled, they get quantized to the platform's + * fixed-point register representation using the LscAlgorithm template parameter + * \a U. + * * \return 0 on success, a negative error code otherwise */ -int LscAlgorithm::configure(lsc::ActiveState &state, const Rectangle &analogCrop, - const std::vector &xPos, - const std::vector &yPos) -{ - LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop; - lsc::ComponentsMap lscData = - impl_->sampleForCrop(analogCrop, xPos, yPos); - - /* - * Retain a copy of the components table. - * - * We could avoid a copy here if getComponents() could - * return sets_.data() but I wasn't able to work around the - * compiler refusing it. - */ - lscData_ = lscData; - - sets_.setData(std::move(lscData)); - state.enabled = true; - - return 0; -} /** * \brief Queue a request to the lsc algorithm @@ -297,9 +370,9 @@ int LscAlgorithm::configure(lsc::ActiveState &state, const Rectangle &analogCrop * Queue a new list of \a controls to the lsc algorithm. * The only supported control is controls::LensShadingCorrectionEnable. */ -void LscAlgorithm::queueRequest(lsc::ActiveState &state, - lsc::FrameContext &context, - const ControlList &controls) +void LscAlgorithmBase::queueRequest(lsc::ActiveState &state, + lsc::FrameContext &context, + const ControlList &controls) { const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); if (lscEnable && *lscEnable != state.enabled) { @@ -322,7 +395,7 @@ void LscAlgorithm::queueRequest(lsc::ActiveState &state, * Populates the list of \a metadata with controls handled by the LscAlgorithm * class. The only supported metadata is controls::LensShadingCorrectionEnable. */ -void LscAlgorithm::process(lsc::FrameContext &context, ControlList &metadata) +void LscAlgorithmBase::process(lsc::FrameContext &context, ControlList &metadata) { metadata.set(controls::LensShadingCorrectionEnable, context.enabled); } @@ -350,8 +423,7 @@ void LscAlgorithm::process(lsc::FrameContext &context, ControlList &metadata) /** * \fn LscAlgorithm::getComponents - * - * Return the map of LSC data per-colour-temperature. + * \brief Return the map of LSC data per-colour-temperature * * Calling this function is only valid after LscAlgorithm::configure() has been * called. An empty components list is returned otherwise. diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 9fe8ad67ea33..30330f284013 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -33,37 +33,96 @@ struct FrameContext { bool update; }; +template +using Components = std::map, std::less<>>; + +template +using ComponentsMap = std::map>; + } /* namespace lsc */ -class LscAlgorithm +#ifndef __DOXYGEN__ +template<> +void Interpolator>:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, + double lambda); +#endif /* __DOXYGEN__ */ + +class LscAlgorithmBase { public: int init(const ValueNode &tuningData, ControlInfoMap::Map &controls, const LscDescriptor &descriptor); - int configure(lsc::ActiveState &state, const Rectangle &analogCrop, - const std::vector &xPos, - const std::vector &yPos); - void queueRequest(lsc::ActiveState &state, lsc::FrameContext &context, const ControlList &controls); void process(lsc::FrameContext &context, ControlList &metadata); - const lsc::Components interpolateComponents(unsigned int ct) +protected: + LscAlgorithmBase() = default; + + std::unique_ptr impl_; + bool polynomial_; +}; + +template +class LscAlgorithm : public LscAlgorithmBase +{ +private: + using Components = lsc::Components; + using ComponentsMap = lsc::ComponentsMap; + +public: + LscAlgorithm() = default; + + int configure(lsc::ActiveState &state, const Rectangle &analogCrop, + const std::vector &xPos, + const std::vector &yPos) + { + LscImplementation::ComponentsMap data = + impl_->sampleForCrop(analogCrop, xPos, yPos); + + ComponentsMap lscData; + for (const auto &[t, c] : data) { + Components &comp = lscData[t]; + + for (const auto &[k, gains] : c) { + auto &quantizedGains = comp[k]; + quantizedGains.reserve(gains.size()); + + for (const float &gain : gains) { + /* + * Tabular LSC tables already store + * quantized values. + */ + if (polynomial_) + quantizedGains.push_back(U(gain).quantized()); + else + quantizedGains.push_back(gain); + } + } + } + + sets_.setData(std::move(lscData)); + state.enabled = true; + + return 0; + } + + const Components interpolateComponents(unsigned int ct) { return sets_.getInterpolated(ct); } - const lsc::ComponentsMap getComponents() + const ComponentsMap &getComponents() const { - return lscData_; + return sets_.data(); } private: - std::unique_ptr impl_; - Interpolator sets_; - lsc::ComponentsMap lscData_; - bool polynomial_; + Interpolator sets_; }; } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index cb770f164d53..baf25df6b908 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -16,49 +16,6 @@ namespace libcamera { namespace ipa { -namespace lsc { - -/** - * \typedef Components - * \brief Associate colour components with a list of gains - * - * LSC tables are defined as a list of gain values associated to a colour - * component. - * - * As different ISPs support different colour components (usually 'r', 'gr', - * 'gb', 'b' or just 'r', 'g', 'b') this class associates a string - * identifier for the colour component to a list of gains. - * - * Each key name shall match an entry in the tuning file. - * - * The list of keys is provided to the LscAlgorithm class using \a - * LscDescriptor::keys. - */ - -/** - * \typedef ComponentsMap - * \brief Associate a colour temperature to a LSC table - * - * An LSC table is generated during the tuning phase for a specific colour - * temperature, and a tuning file usually contains LSC tables generated for - * several different colour temperatures. - */ - -} /* namespace lsc */ - -#ifndef __DOXYGEN__ -template<> -void Interpolator:: - interpolate(const lsc::Components &a, - const lsc::Components &b, - lsc::Components &dest, - double lambda) -{ - for (auto const &[k, v] : a) - interpolateVector(v, b.at(k), dest[k], lambda); -} -#endif - /** * \struct LscDescriptor * \brief Describe the ISP LSC engine @@ -90,6 +47,38 @@ void Interpolator:: * Defines the interface for the LSC algorithm implementation. */ +/** + * \typedef LscImplementation::Components + * \brief Associate colour components with a list of gains in float format + * + * LSC tables are defined as a list of gain values associated to a colour + * component. + * + * As different ISP support different colour components (usually 'r', 'gr', + * 'gb', 'b' or just 'r', 'g', 'b') this class associates a string + * identifier for the colour component to a list of gains. + * + * The gain values are represented as floats and are either loaded from tuning + * file or are the result of a polynomial expansion, depending on the + * LscImplementation derived class in use (LscTable or LscPolynomial). + * + * The gain values are converted to the platform's register format before being + * returned to the IPA module by LscAlgorithm::configure(). + * + * \todo Tabular LSC data are already expressed in register format. Make the + * tuning files express gains in float format as well and actually perform + * quantization in LscAlgorithm::configure(). + */ + +/** + * \typedef LscImplementation::ComponentsMap + * \brief Associate a colour temperature to a LSC table + * + * An LSC table is generated during the tuning phase for a specific colour + * temperature, and a tuning file usually contains LSC tables generated for + * several different colour temperatures. + */ + /** * \fn LscImplementation::~LscImplementation * \brief Virtual class destructor diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 2cff34b32a2b..d53736cb71d3 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -24,32 +24,6 @@ namespace libcamera { namespace ipa { -namespace lsc { - -using Components = std::map, std::less<>>; -using ComponentsMap = std::map; - -} /* namespace lsc */ - -#ifndef __DOXYGEN__ -template -void interpolateVector(const std::vector &a, const std::vector &b, - std::vector &dest, double lambda) -{ - ASSERT(a.size() == b.size()); - dest.resize(a.size()); - for (size_t i = 0; i < a.size(); i++) - dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda; -} - -template<> -void Interpolator:: - interpolate(const lsc::Components &a, - const lsc::Components &b, - lsc::Components &dest, - double lambda); -#endif /* __DOXYGEN__ */ - struct LscDescriptor { std::vector keys; unsigned int numHSamples; @@ -60,12 +34,15 @@ struct LscDescriptor { class LscImplementation { public: + using Components = std::map, std::less<>>; + using ComponentsMap = std::map; + virtual ~LscImplementation() {} virtual int parseLscData(const ValueNode &tuningData, const LscDescriptor &descriptor) = 0; - virtual lsc::ComponentsMap + virtual ComponentsMap sampleForCrop(const Rectangle &cropRectangle, std::vector xPos, std::vector yPos) = 0; }; diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index 419de2c12cf0..6c38c7f7eeb1 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -194,15 +194,15 @@ int LscPolynomial::parseLscData(const ValueNode &sets, * [0, 0.0625, 0.125, 0.1875, ... , 1]. It is expected that the first position * is 0 and the last position is 1. */ -lsc::ComponentsMap +LscImplementation::ComponentsMap LscPolynomial::sampleForCrop(const Rectangle &cropRectangle, std::vector xPos, std::vector yPos) { - lsc::ComponentsMap components; + LscImplementation::ComponentsMap components; for (const auto &[t, c] : lscData_) { - lsc::Components &comp = components[t]; + LscImplementation::Components &comp = components[t]; for (const auto &[k, p] : c) comp.try_emplace(k, samplePolynomial(p, xPos, yPos, diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 24b216a1aa25..b9d3d834662b 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -60,7 +60,7 @@ public: int parseLscData(const ValueNode &sets, const LscDescriptor &descriptor) override; - lsc::ComponentsMap + LscImplementation::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, std::vector xPos, std::vector yPos) override; diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp index 3e8b1a060ce7..74ea8a7266e1 100644 --- a/src/ipa/libipa/lsc_table.cpp +++ b/src/ipa/libipa/lsc_table.cpp @@ -59,7 +59,7 @@ int LscTable::parseLscData(const ValueNode &sets, int LscTable::parseLscComponent(const ValueNode &yamlSet, unsigned int ct, const LscDescriptor &descriptor) { - lsc::Components component; + LscImplementation::Components component; for (auto &k : descriptor.keys) { auto [it, inserted] = component.try_emplace(k, parseTable(yamlSet, diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index 6d9b0c692b7f..9f39b38e258c 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -29,7 +29,7 @@ public: int parseLscData(const ValueNode &sets, const LscDescriptor &descriptor) override; - lsc::ComponentsMap + LscImplementation::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, [[maybe_unused]] std::vector xPos, [[maybe_unused]] std::vector yPos) override @@ -47,7 +47,7 @@ private: unsigned int numHSamples, unsigned int numVSamples); - lsc::ComponentsMap lscData_; + LscImplementation::ComponentsMap lscData_; }; } /* namespace ipa */ diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 911ac6895a14..f2858656023e 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -175,23 +175,16 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) } void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, - const lsc::Components &set) + const lsc::Components &set) { - /* - * The hardware uses 2.10 fixed point format and limits the legal values - * to [1..3.999]. Scale and clamp the sampled values accordingly. - */ - const auto quantizeSet = [&](const std::string &key, uint16_t *dst) { - const auto &s = set.at(key); - std::transform(s.begin(), s.end(), dst, [](float f) { - return std::clamp(f * 1024, 1024, 4095); - }); - }; - - quantizeSet("r", &config.r_data_tbl[0][0]); - quantizeSet("gr", &config.gr_data_tbl[0][0]); - quantizeSet("gb", &config.gb_data_tbl[0][0]); - quantizeSet("b", &config.b_data_tbl[0][0]); + const auto &r = set.at("r"); + std::copy(r.begin(), r.end(), &config.r_data_tbl[0][0]); + const auto &gr = set.at("gr"); + std::copy(gr.begin(), gr.end(), &config.gr_data_tbl[0][0]); + const auto &gb = set.at("gb"); + std::copy(gb.begin(), gb.end(), &config.gb_data_tbl[0][0]); + const auto &b = set.at("b"); + std::copy(b.begin(), b.end(), &config.b_data_tbl[0][0]); } /** @@ -241,8 +234,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, setParameters(*config); - const lsc::Components &set = lscAlgo_.interpolateComponents(quantizedCt); - copyTable(*config, set); + copyTable(*config, lscAlgo_.interpolateComponents(quantizedCt)); lastAppliedCt_ = ct; lastAppliedQuantizedCt_ = quantizedCt; diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 63f0887f0838..c1f8904426c0 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -12,6 +12,7 @@ #include #include "libcamera/internal/value_node.h" +#include "libipa/fixedpoint.h" #include "libipa/lsc.h" @@ -41,7 +42,8 @@ public: ControlList &metadata) override; private: void setParameters(rkisp1_cif_isp_lsc_config &config); - void copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set0); + void copyTable(rkisp1_cif_isp_lsc_config &config, + const lsc::Components &set); std::vector xSize_; std::vector ySize_; @@ -55,7 +57,7 @@ private: unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; - LscAlgorithm lscAlgo_; + LscAlgorithm> lscAlgo_; }; } /* namespace ipa::rkisp1::algorithms */ From patchwork Wed Aug 5 16:13:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27649 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 AFF43C334A for ; Wed, 5 Aug 2026 16:13:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4157368178; Wed, 5 Aug 2026 18:13:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="VUO/vHu4"; 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 7E34E68143 for ; Wed, 5 Aug 2026 18:13:43 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AABE03AE4; Wed, 5 Aug 2026 18:12:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946353; bh=u9V2JQQr/lPa1Mq/Jr0a/zN5b1A53nax81FRD0HfLtU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=VUO/vHu4HtC6QH3pGvxac8zIXiySAPCW58IW81yDftvvx4V9w2iJVtI6af1YGmpAa 4GlkUt3/pjv1AcsA2kuVVLtcKLeaw0gxY58BpD3DcqwmidYwgk4hkZYzanIwpRKT2+ ACEy6yKiQ5BHzoyRndF2l7ZmX/uitq7x2wrftB/w= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:11 +0200 Subject: [PATCH v7 29/32] ipa: libipa: lsc: Re-sort LscAlgorithmBase documentation MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-29-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=6732; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=u9V2JQQr/lPa1Mq/Jr0a/zN5b1A53nax81FRD0HfLtU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EiBKf3Y5S3cpZARC9ZusmHEl3vjyMHioNb2 afQwmFnZ/qJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIgAKCRByNAaPFqFW PJ1dD/sHz/j7GhS+cghsIfoljtP3WbQbfphnUl/qJEC9DXvGI2iJmSxLpOvVcFlyo2D3AidrAQZ OHBcZDNUvNRE5ZNboaQF7uiH+MJnZrMMB7M7Wz4gxQT41qWmPC5MAjEdga/SL+rLiQdHbOEd4fb Dmvfunbr0MeLdeMFb67Wh/m9UodOmV0UAPT8LpepIccNDyH12hc/MEAhuZni3elHEIllRFHurDs Vk/nK4s4NA9iWPapjDwKgxz5Aa/8yIFsRENSPD3BjwbNM8UfCy34ibZ3r3zl4fEC4apzXuNfse+ LeUY6vZ5DJvvYG895ZKk0ISA++SBqxNsoF+KOqajwGKAXTG/nf2DU1MKJ4ZlVtNuijAB1pC0wwA 8ySSEWMBn+RDxFS4Y4w8OKAPb8WzElQr657I312neZSiygpLRxFbxKRxyaxSStZq13Y823L54nv CB5K6Z77y62aBcC1xSyQNXS5GXYY+E6ilNUPmDCJhBZtwlQLCrIww61qSpEyq8f0Z1eq07z3Hd0 ZzAGqTVQ9mJRVITEZNFL+IqO7s2Ba3LiRscn04su85d3RvUOYgl6vbWHhi6wOF1aR5W2s8YY5p9 bI6061lcb//XgY7dio3Fkt8s+pU6CYDB1CW+IKw93AffKNp9MS/wYYxPjVwopOBC1e0fywUwm4q +4f9mB8mJyAydEQ== 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" The documentation had not been resorted in the previous patch to keep the diff short. Re-sort the documentation of the LscAlgorithmBase and LscAlgorithm classes to match the declaration order. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc.cpp | 168 ++++++++++++++++++++++++------------------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index bb02290fc9ad..a8b89bcedf57 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -111,6 +111,90 @@ void Interpolator>:: * Base class for LscAlgorithm for non-templated functions implementation */ +/** + * \param[in] tuningData The tuning data + * \param[in] controls The IPA list of supported controls + * \param[in] descriptor The LSC engine descriptor + * + * Parse \a tuningData according to the settings specified in \a descriptor to + * populate the LSC data and registers LSC controls in \a controls. + * + * \return 0 on success, a negative error code otherwise + */ +int LscAlgorithmBase::init(const ValueNode &tuningData, ControlInfoMap::Map &controls, + const LscDescriptor &descriptor) +{ + polynomial_ = false; + + std::string type = tuningData["type"].get("table"); + if (type == "table") { + impl_ = std::make_unique(); + LOG(Lsc, Debug) << "Using table-based Lsc"; + } else if (type == "polynomial") { + impl_ = std::make_unique(); + polynomial_ = true; + LOG(Lsc, Debug) << "Using polynomial Lsc"; + } else { + LOG(Lsc, Error) << "Unsupported Lsc algorithm '" + << type << "'"; + return -EINVAL; + } + + const ValueNode &yamlSets = tuningData["sets"]; + if (!yamlSets.isList()) { + LOG(Lsc, Error) << "'sets' parameter not found in tuning file"; + return -EINVAL; + } + + int ret = impl_->parseLscData(yamlSets, descriptor); + if (ret) + return ret; + + controls[&controls::LensShadingCorrectionEnable] = + ControlInfo(false, true, true); + + return 0; +} + +/** + * \brief Queue a request to the lsc algorithm + * \param[in] state The lsc active state + * \param[in] context The lsc frame context + * \param[in] controls The list of controls associated with a Request + * + * Queue a new list of \a controls to the lsc algorithm. + * The only supported control is controls::LensShadingCorrectionEnable. + */ +void LscAlgorithmBase::queueRequest(lsc::ActiveState &state, + lsc::FrameContext &context, + const ControlList &controls) +{ + const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); + if (lscEnable && *lscEnable != state.enabled) { + state.enabled = *lscEnable; + + LOG(Lsc, Debug) + << (state.enabled ? "Enabling" : "Disabling") << " Lsc"; + + context.update = true; + } + + context.enabled = state.enabled; +} + +/** + * \brief Populate the list of lsc metadata + * \param[in] context The lsc frame context + * \param[in] metadata The list of metadata + * + * Populates the list of \a metadata with controls handled by the LscAlgorithm + * class. The only supported metadata is controls::LensShadingCorrectionEnable. + */ +void LscAlgorithmBase::process(lsc::FrameContext &context, ControlList &metadata) +{ + metadata.set(controls::LensShadingCorrectionEnable, context.enabled); +} + /** * \var LscAlgorithmBase::impl_ * \brief The LSC algorithm implementation @@ -287,51 +371,6 @@ void Interpolator>:: * can retrieve them using LscAlgorithm::getComponents(). */ -/** - * \param[in] tuningData The tuning data - * \param[in] controls The IPA list of supported controls - * \param[in] descriptor The LSC engine descriptor - * - * Parse \a tuningData according to the settings specified in \a descriptor to - * populate the LSC data and registers LSC controls in \a controls. - * - * \return 0 on success, a negative error code otherwise - */ -int LscAlgorithmBase::init(const ValueNode &tuningData, ControlInfoMap::Map &controls, - const LscDescriptor &descriptor) -{ - polynomial_ = false; - - std::string type = tuningData["type"].get("table"); - if (type == "table") { - impl_ = std::make_unique(); - LOG(Lsc, Debug) << "Using table-based Lsc"; - } else if (type == "polynomial") { - impl_ = std::make_unique(); - polynomial_ = true; - LOG(Lsc, Debug) << "Using polynomial Lsc"; - } else { - LOG(Lsc, Error) << "Unsupported Lsc algorithm '" - << type << "'"; - return -EINVAL; - } - - const ValueNode &yamlSets = tuningData["sets"]; - if (!yamlSets.isList()) { - LOG(Lsc, Error) << "'sets' parameter not found in tuning file"; - return -EINVAL; - } - - int ret = impl_->parseLscData(yamlSets, descriptor); - if (ret) - return ret; - - controls[&controls::LensShadingCorrectionEnable] = - ControlInfo(false, true, true); - - return 0; -} - /** * \fn LscAlgorithm::configure() * \brief Re-sample and quantize LSC data @@ -361,45 +400,6 @@ int LscAlgorithmBase::init(const ValueNode &tuningData, ControlInfoMap::Map &con * \return 0 on success, a negative error code otherwise */ -/** - * \brief Queue a request to the lsc algorithm - * \param[in] state The lsc active state - * \param[in] context The lsc frame context - * \param[in] controls The list of controls associated with a Request - * - * Queue a new list of \a controls to the lsc algorithm. - * The only supported control is controls::LensShadingCorrectionEnable. - */ -void LscAlgorithmBase::queueRequest(lsc::ActiveState &state, - lsc::FrameContext &context, - const ControlList &controls) -{ - const auto &lscEnable = controls.get(controls::LensShadingCorrectionEnable); - if (lscEnable && *lscEnable != state.enabled) { - state.enabled = *lscEnable; - - LOG(Lsc, Debug) - << (state.enabled ? "Enabling" : "Disabling") << " Lsc"; - - context.update = true; - } - - context.enabled = state.enabled; -} - -/** - * \brief Populate the list of lsc metadata - * \param[in] context The lsc frame context - * \param[in] metadata The list of metadata - * - * Populates the list of \a metadata with controls handled by the LscAlgorithm - * class. The only supported metadata is controls::LensShadingCorrectionEnable. - */ -void LscAlgorithmBase::process(lsc::FrameContext &context, ControlList &metadata) -{ - metadata.set(controls::LensShadingCorrectionEnable, context.enabled); -} - /** * \fn LscAlgorithm::interpolateComponents * \brief Interpolate the LSC tables for a given colour temperature From patchwork Wed Aug 5 16:13:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27653 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 699C2C334E for ; Wed, 5 Aug 2026 16:14:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B498568193; Wed, 5 Aug 2026 18:14:01 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mjrtHv2h"; 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 697F86815F for ; Wed, 5 Aug 2026 18:13:44 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 753E15B3; Wed, 5 Aug 2026 18:12:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946354; bh=ax8BgGImoAq8vCACjcTubXq3ibzUonWpyk48NOKhiQI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=mjrtHv2hN2ts8EiNsXEbu4BuI3G4+btM0Y/LxOFuCuxZD4V3MDAwH7lSPk3zMXxsW EGDDfhMIKI3fffcdfbMJ+vOW6ff935/o6/LYs8B5sPxMXV0QjN4G2+5Tnee5ZBtxVd DEzNq4eqlWzNMf0M57CF0jTK3UtuWVBDs8hQYG6w= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:12 +0200 Subject: [PATCH v7 30/32] ipa: mali-c55: Add sensorInfo to IPAContext MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-30-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=1573; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ax8BgGImoAq8vCACjcTubXq3ibzUonWpyk48NOKhiQI=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EiUpAWHHCka/O7ihgM4tKG9afNQUez1ylZz 5NHF+4idJGJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIgAKCRByNAaPFqFW PJFZEADBu4kGMgD3nw8amu1INErWS03MVcKo87KuSETqhdaHal4MxU7PRFpw8RRLY8lpiWUGxw2 KzccLG83EnjcRH9MpG0Vmy9aRClX3DuL4l50Rh0cn7mhP45zKqG/PSviEiIcBmEYphVVqnTjiFe fwaJO1XjziCZcoq/5lLl3h4AXSJTeOQYrB6gWrZL9l8DPjo9qwp1o8fyIh3FadbWlPTifsyCCKY PU/51O/qzGRqrhxR0az4h47JLrQa9aDy8b/r1buljBTlveaBER0zvdmyKVU+0C9WjZ13HNWLgRk w4qTorkr9nwzm46eAP7fcei7SA5Ne3vBKihFQtlKU3AK0olBdUGRBVL/m0b8KMjQUDDITQwqQ5Q OFkOmazp8rZ1emjNZaWoukBZHcEzrwRPEWcpTjgiJlnep1++LTyHa2RzeFjxf3NjQomqat0wisn QIxIKqs2277C0R6rxb9rw5XNBxnuDI4dX6fcGu7Tgcw4EeyUnHl/1ULanCkheLluaSd5Km1MG/G Fz22u3f6pum59RospY3gQ4qLWtV94k/OFZ0EWfSGM+rSd8fWZ7aliWa0MAIJv3rsvKJ5vaNlH91 otK/45ZIcKipI36he15blgrV2QG3e5c5tEFi51ITZtPr7iWoBKuYObmBpyGdu9IJvuuzkdlSJ0E ds0ZYFz8ovqIvBg== 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" Store the sensorInfo in the IPAContext. It will be used by the Lsc algorithm to support polynomial LSC tables. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally --- src/ipa/mali-c55/ipa_context.h | 3 +++ src/ipa/mali-c55/mali-c55.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index ef3188e347d7..deb3f39e0857 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -10,6 +10,8 @@ #include #include +#include + #include "libcamera/internal/bayer_format.h" #include @@ -77,6 +79,7 @@ struct IPAContext { { } + IPACameraSensorInfo sensorInfo; IPASessionConfiguration configuration; IPAActiveState activeState; diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 1d3af0627fdb..da0dbe4b31b8 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -101,6 +101,8 @@ std::string IPAMaliC55::logPrefix() const int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, ControlInfoMap *ipaControls) { + context_.sensorInfo = ipaConfig.sensorInfo; + camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!camHelper_) { LOG(IPAMaliC55, Error) From patchwork Wed Aug 5 16:13:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27654 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 53822C334F for ; Wed, 5 Aug 2026 16:14:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B0D596818D; Wed, 5 Aug 2026 18:14:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Hxc5IL7R"; 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 2D47B68173 for ; Wed, 5 Aug 2026 18:13:45 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3C5B0399E; Wed, 5 Aug 2026 18:12:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946354; bh=s1u/o5LJtpJV5RYYO5ncopNRRj9h66VQlbCPcLfc9xg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Hxc5IL7R27fkOuTrhnE/XNbh6aUfPeeQNC4jHBxuj9WnukBoJaqI0SjhVJxmsREL6 3zgM2Z/zkXeMaSTgzikthDvmXjeNV3XLxyDMjvsDIRFxdV1TLJ/zAyuEFaYSLu1JSP 8+9aPWJeIAzN0UOQWwq1HFPM4KI6aOLUfb7mvanQ= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:13 +0200 Subject: [PATCH v7 31/32] ipa: mali-c55: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-31-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=10996; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=s1u/o5LJtpJV5RYYO5ncopNRRj9h66VQlbCPcLfc9xg=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2Ejc/uq24W7odWcVU3ismupd3vVpCPXA7YhO ajJkci1bGmJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIwAKCRByNAaPFqFW PBYVEACeFlBS1sqk5h+xMncVdDQ9Lb9/AmwM4mjvd5ynMQ6mryL/3AYfK4BruxuBbKqIAZOFQfx 54QSHFRnssG5PYXebWLtKm5imKi8CWChRkHvkcxcVRLeg923+pNVrxzXCZLoRkyfyaXHOBi+G1c kINimWbx02//RkwSBPGbrtmaukfXkfca6QNPDJgY1BHrohwwZZoBeuwO5bpHf2IUkyNiwStI18j wGYdSCvAFqfKAHU7ivUcfd/JiNDKvxsbBezN1C1kcxdhUH6LL9z0qwkEp/tZSqjN3G5jIPL6nqK 496R6GHGXK4M6MP0UaEpJcsNFvLQuhtaH3WorIsvIe+JBR13Na4Dlkg+PW9nrOy8xtcvkJcIxz6 xlPSMXyzoR3eNtgEUD3XulE8E+wXCqHd4IxxTfloQoUt3U7IrAHWkkb/NizUzsHpcb3kfkw38hw j5hjF93vUAD5IrjN/SNVB055YuxfXm4zJm2TmZW7rOSMSzVFqX0ErlSU9tePzDb5Z4OOxuIjuta vnanhQejhVJUeFBOKUAC6zhAewr+KbcFYj68nlgCW9JGv1MUM6/yr+1doJfpeb/bjDGvhUPkcwk aiMo5akyeKb1MfaoMWA2B8JNW1EYYXGS0ENCeHzJM4CEnTXXf2UYY0vO4tWoVfn3latWBGuiiMf OLlc6JdmuNVmbEw== 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" Port the Mali-C55 LSC algorithm to use libIPA LscAlgorithm. As we now need to specify the expected grid size at LscAlgorithm::init() time, only tables of 32x32 are now supported. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally --- src/ipa/mali-c55/algorithms/lsc.cpp | 160 +++++++++++++++++++++--------------- src/ipa/mali-c55/algorithms/lsc.h | 32 ++++++-- src/ipa/mali-c55/ipa_context.h | 3 + 3 files changed, 123 insertions(+), 72 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp index 097239dcef3e..fff0dc7d0e64 100644 --- a/src/ipa/mali-c55/algorithms/lsc.cpp +++ b/src/ipa/mali-c55/algorithms/lsc.cpp @@ -7,40 +7,77 @@ #include "lsc.h" +#include #include -#include "libcamera/internal/value_node.h" - namespace libcamera { namespace ipa::mali_c55::algorithms { LOG_DEFINE_CATEGORY(MaliC55Lsc) -int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) +/* Gain values in [1, 5] range */ +static constexpr unsigned int kMeshScale = 6; + +/* Mali-C55 hw supports configurable mesh sizes; we fix it to 32. */ +static constexpr unsigned int kMeshSize = 32; +static constexpr unsigned int kGridSize = kMeshSize * kMeshSize; + +/* Per-colour component page offsets in the mesh table. */ +static constexpr unsigned int kRedOffset = 0; +static constexpr unsigned int kGreenOffset = 1024; +static constexpr unsigned int kBlueOffset = 2048; + +/* + * \todo Clarify if Mali-C55 can support up to 4 colour temperatures. + * + * The uAPI only expose MALI_C55_NUM_MESH_SHADING_ELEMENTS (3072) gain elements, + * which correspond to three pages of 1024 (32x32) entries. + */ +static constexpr unsigned int kMaxColourTemperatures = 3; + +/* + * The LSC algorithm implementation only supports 32x32 grids. Create a list of + * positions from the grid size. + */ +std::vector Lsc::segmentsToPosition() const { - if (!tuningData.contains("meshScale")) { - LOG(MaliC55Lsc, Error) << "meshScale missing from tuningData"; - return -EINVAL; - } + std::vector positions(kMeshSize); + for (double i = 0.0; i < kMeshSize; ++i) + positions[i] = i / (kMeshSize - 1); - meshScale_ = tuningData["meshScale"].get(0); + return positions; +} - const ValueNode &sets = tuningData["sets"]; - if (!sets.isList()) { - LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid"; - return -EINVAL; - } +int Lsc::init(IPAContext &context, const ValueNode &tuningData) +{ + gridPos_ = segmentsToPosition(); + + return lscAlgo_.init(tuningData, context.ctrlMap, { + .keys = { "r", "g", "b" }, + .numHSamples = kMeshSize, + .numVSamples = kMeshSize, + .sensorSize = context.sensorInfo.activeAreaSize + }); +} - size_t tableSize = 0; - for (const auto &set : sets.asList()) { - uint32_t ct = set["ct"].get(0); +int Lsc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) +{ + int ret = lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop, + gridPos_, gridPos_); + if (ret) + return ret; - if (!ct) { - LOG(MaliC55Lsc, Error) << "Invalid colour temperature"; - return -EINVAL; - } + /* Re-initialize the mesh tables and reserve space for enough entries. */ + mesh_ = std::vector(kGridSize * kMaxColourTemperatures); + colourTemperatures_.clear(); + /* + * Get the lsc tables per colour components and populate mesh_ with + * their content. + */ + auto &components = lscAlgo_.getComponents(); + for (auto const &[ct, component] : components) { if (std::count(colourTemperatures_.begin(), colourTemperatures_.end(), ct)) { LOG(MaliC55Lsc, Error) @@ -48,44 +85,26 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) return -EINVAL; } - std::vector rTable = - set["r"].get>().value_or(utils::defopt); - std::vector gTable = - set["g"].get>().value_or(utils::defopt); - std::vector bTable = - set["b"].get>().value_or(utils::defopt); + const std::vector &rTable = component.at("r"); + const std::vector &gTable = component.at("g"); + const std::vector &bTable = component.at("b"); - /* - * Some validation to do; only 16x16 and 32x32 tables of - * coefficients are acceptable, and all tables across all of the - * sets must be the same size. The first time we encounter a - * table we check that it is an acceptable size and if so make - * sure all other tables are of equal size. - */ - if (!tableSize) { - if (rTable.size() != 256 && rTable.size() != 1024) { - LOG(MaliC55Lsc, Error) - << "Invalid table size for colour temperature " << ct; - return -EINVAL; - } - tableSize = rTable.size(); - } - - if (rTable.size() != tableSize || - gTable.size() != tableSize || - bTable.size() != tableSize) { - LOG(MaliC55Lsc, Error) - << "Invalid or mismatched table size for colour temperature " << ct; - return -EINVAL; - } + /* Only 32x32 tables of coefficients are accepted. */ + ASSERT(rTable.size() == kGridSize && + gTable.size() == kGridSize && + bTable.size() != kGridSize); - if (colourTemperatures_.size() >= 3) { + if (colourTemperatures_.size() >= kMaxColourTemperatures) { LOG(MaliC55Lsc, Error) << "A maximum of 3 colour temperatures are supported"; return -EINVAL; } - for (unsigned int i = 0; i < tableSize; i++) { + /* + * Create the mesh table entries by assembling up to 3 gains per + * colour temperature in a u32 word. + */ + for (unsigned int i = 0; i < kGridSize; i++) { mesh_[kRedOffset + i] |= (rTable[i] << (colourTemperatures_.size() * 8)); mesh_[kGreenOffset + i] |= @@ -97,35 +116,36 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) colourTemperatures_.push_back(ct); } - /* - * The mesh has either 16x16 or 32x32 nodes, we tell the driver which it - * is based on the number of values in the tuning data's table. - */ - if (tableSize == 256) - meshSize_ = 15; - else - meshSize_ = 31; - return 0; } +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, + controls); +} + void Lsc::fillConfigParamsBlock(MaliC55Params *params) const { auto block = params->block(); block->mesh_show = false; - block->mesh_scale = meshScale_; + block->mesh_scale = kMeshScale; block->mesh_page_r = 0; block->mesh_page_g = 1; block->mesh_page_b = 2; - block->mesh_width = meshSize_; - block->mesh_height = meshSize_; + block->mesh_width = kMeshSize - 1; + block->mesh_height = kMeshSize - 1; std::copy(mesh_.begin(), mesh_.end(), block->mesh); } void Lsc::fillSelectionParamsBlock(MaliC55Params *params, uint8_t bank, - uint8_t alpha) const + uint8_t alpha) const { auto block = params->block(); @@ -198,6 +218,18 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, fillConfigParamsBlock(params); } +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Lsc::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const mali_c55_stats_buffer *stats, + ControlList &metadata) +{ + lscAlgo_.process(frameContext.lsc, metadata); +} + REGISTER_IPA_ALGORITHM(Lsc, "Lsc") } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h index 3d35fd72bfa8..5f752eebaef5 100644 --- a/src/ipa/mali-c55/algorithms/lsc.h +++ b/src/ipa/mali-c55/algorithms/lsc.h @@ -5,10 +5,19 @@ * Mali-C55 Lens shading correction algorithm */ -#include +#include #include +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/fixedpoint.h" +#include "libipa/lsc.h" + #include "algorithm.h" +#include "ipa_context.h" +#include "params.h" namespace libcamera { @@ -21,23 +30,30 @@ public: ~Lsc() = default; int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, MaliC55Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + ControlList &metadata) override; private: - static constexpr unsigned int kRedOffset = 0; - static constexpr unsigned int kGreenOffset = 1024; - static constexpr unsigned int kBlueOffset = 2048; - + std::vector segmentsToPosition() const; void fillConfigParamsBlock(MaliC55Params *params) const; void fillSelectionParamsBlock(MaliC55Params *params, uint8_t bank, uint8_t alpha) const; std::tuple findBankAndAlpha(uint32_t ct) const; - std::vector mesh_ = std::vector(3072); std::vector colourTemperatures_; - uint32_t meshScale_; - uint32_t meshSize_; + std::vector mesh_; + + std::vector gridPos_; + + LscAlgorithm> lscAlgo_; }; } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index deb3f39e0857..7017dfc87c2d 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -19,6 +19,7 @@ #include "libipa/awb.h" #include "libipa/ccm.h" #include "libipa/fixedpoint.h" +#include "libipa/lsc.h" namespace libcamera { @@ -60,6 +61,7 @@ struct IPAActiveState { ipa::awb::ActiveState awb; ipa::ccm::ActiveState ccm; + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -71,6 +73,7 @@ struct IPAFrameContext : public FrameContext { ipa::awb::FrameContext awb; ipa::ccm::FrameContext ccm; + ipa::lsc::FrameContext lsc; }; struct IPAContext { From patchwork Wed Aug 5 16:13:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27655 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 05577C3350 for ; Wed, 5 Aug 2026 16:14:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 781C668197; Wed, 5 Aug 2026 18:14:03 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="hsrzULVj"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AF1416816E for ; Wed, 5 Aug 2026 18:13:45 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-25-35.net.vodafone.it [109.113.25.35]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EF3953B23; Wed, 5 Aug 2026 18:12:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1785946355; bh=XBc9Oh6IdqRxVsjpBLv31tO74D62Y2hjOtpHW69RQec=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=hsrzULVjf1V8sLdnSYGjYmFuf1sGxIwjJ17Z/FzhbpaBwePyuiV+yTmBYSQreSjzM vOgmHkcpPkmXj5h2eXcX5yRBbknvHSxOhdIswaSv+Ta7erGvL8Wl+E8OfzhjynsQZt /J73A+gda5NUyfhwYpsF66bH/hurMcwUmb2YO5Xs= From: Jacopo Mondi Date: Wed, 05 Aug 2026 18:13:14 +0200 Subject: [PATCH v7 32/32] ipa: libipa: lsc: Specialize Interpolator for uint8_t MIME-Version: 1.0 Message-Id: <20260805-libipa-algorithms-v7-32-7425b5b795d4@ideasonboard.com> References: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> In-Reply-To: <20260805-libipa-algorithms-v7-0-7425b5b795d4@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug , Milan Zamazal Cc: Jacopo Mondi X-Mailer: b4 0.16-dev X-Developer-Signature: v=1; a=openpgp-sha256; l=1666; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=XBc9Oh6IdqRxVsjpBLv31tO74D62Y2hjOtpHW69RQec=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqc2EjKjdqzlqfEzWu+iIXxkK5IEVCWSk3nsxXF lFU7XoUHsSJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCanNhIwAKCRByNAaPFqFW PB3uD/0fRq+cobLRAa2IDEpweinlcEVPdTDZwuSBxm0+2cPb7MabunKk+DFqSfd4QPl1zT2E3pD QHhcBZEh9ldBSiD80Tt/m3MyZJo4dwo8k17Y0aeGuokinCUOnqv8iyjG6lHsYZ1mG+Y4dVX4skk GIb3vqPR1/zHkgJCeGt4FfoI8jvDcT5AU89Z3g/Jwjt8gYDY6agMpfUjIU5PdAmpaBdvUSFE9S5 dF/+1XCWwvCnW+w5KRxbpJpxb4ISB4z+YqgMAI58kzC8PCjLSeboDmrj1YdfEeULeKLUqR0m5HL vsyAnKKekjT28DrwxUQpiNOcW08SA606J1jAMoMN8xMw9C9LzR0mv8eldBEH/NWnnV79OYsCYPx nEtZL7FzLFulXBXZEOTyUeVaFQTnbQH7fqmyli7JyQ69UXPH5mGTJ+J+TXbN7Tp979lpRgiyKgE TVRNZSmdRqPgYdnBDdclOEAm7PuibF6kDB8O2WLLk3n9eObqabpQdfFpYZEt4hUGNrdup0AjLUi IBfORtXLlp+dd7WXU3At1vFBJ+2VxkuQ4qanN3wog1+qwwxc2xrrvlDTw1B6svbYlDhV/p9kLYX oZuVitkTywokz0muijdWlQPCJmwEpPrvfbiHYvB7SvIar4HUaDZAfthaHp5oag0108WfI9YAuB9 VH65fxdftZCYbxw== 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" Specialize Interpolator<>::interpolate() for uint8_t register size. Some platforms (Mali, Simple) use an 8 bit register size. Provide a specialization for the that type of Interpolator<>::interpolate(). Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/lsc.cpp | 11 +++++++++++ src/ipa/libipa/lsc.h | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index a8b89bcedf57..cf9ea6e68c73 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -102,6 +102,17 @@ void Interpolator>:: for (auto const &[k, v] : a) interpolateVector(v, b.at(k), dest[k], lambda); } + +template<> +void Interpolator>:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, + double lambda) +{ + for (auto const &[k, v] : a) + interpolateVector(v, b.at(k), dest[k], lambda); +} #endif /** diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 30330f284013..17d5a4a141c1 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -48,6 +48,13 @@ void Interpolator>:: const lsc::Components &b, lsc::Components &dest, double lambda); + +template<> +void Interpolator>:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, + double lambda); #endif /* __DOXYGEN__ */ class LscAlgorithmBase