From patchwork Wed Jul 8 09:24: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: 27266 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 C200EC3301 for ; Wed, 8 Jul 2026 09:25:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9440266083; Wed, 8 Jul 2026 11:25:07 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ND/gI7Xu"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9F55865FEF for ; Wed, 8 Jul 2026 11:25:05 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A226A19A2; Wed, 8 Jul 2026 11:24:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502655; bh=RpUDcWqrtYtmnystDS0mFOGu34COlhfbnzDYcV9x7j0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ND/gI7XuJquU71JQfDUMODbbCj13O/Mx5Rfx6Tc5yFG0txi5qoopNOe1/75GmHnIu 11owXNXXekNUXz43ObHHtfbYDnJGSFwf3g1ftxHq1j271IbYw4VLLVKVFrfW5KI8RO 8XJIHR95WRR9nMYC2jPFt7v+LlIZ9o17x4xHb5Zo= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:48 +0200 Subject: [PATCH v4 01/35] ipa: libipa: awb: Reimplement AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-1-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=45702; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=RpUDcWqrtYtmnystDS0mFOGu34COlhfbnzDYcV9x7j0=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdraywfeh4tysEXG5oqlNlapy/9Q2vnSa0iN EEMLSlcQNCJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XawAKCRByNAaPFqFW PMnED/9Mp7erkb4854X7FEReSl41v+refnvPW6D1TsuGNcntl9tNPQSOIvcQEmNHtUb1xraZgym 6zYooryEhCgDy2XRzANInDQoXBJPKNOQFV+SsXNgDIxFPukSuZ4ySFtJQeToc6spG7Y1n/ZL9tw DKo1tz+XGu7BwVXIdSLI/2h+SAOeoieWv6XnPOHTNwogetHi47Tuhs5rAoGdr3JxfVyOvKNEIGe 845h3pgn4IsTB3V2jwiMnTQGywr7ZwC0ZLiPrvw0vibzCBZZX4lk2TyPXiOEUG/06uEewwnD4DC auk2k3NmWystaOAF+jpX+sMDdYKbuJQHOJoRBqs6YCJJo1sLSk+DlXhzZee+mNqZVQN7xINtTbj y0lx5ACpO8aUtADWKNMt452VCqoeafnlYoezb1hneyutOA8xaYJG31L5Ge3tQy3BiT78vmRpv+m EuK4HashnPmzEB8pZyIR3BMs6pKYNxFkAWLe286V8sXl3ntbXVZr1uHIuG04ZSQARfaihUv5DFt AwSl3ds+r2Fkmi68j9AaqkaYbVnyjO/00vivZKd42HBwQVqd9NKQcqHG1ZvxBHNOiOgcyfUUouV 24I+uZSVq5mK905eibEthRBea//0oZSslMZ8yjdkir4MzVdAWV9iJA1IEX3fx3vIHhnoFK0NrpA tNJ5sJF4Qzx2jlQ== 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 Jul 8 09:24: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: 27267 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 337BBC3301 for ; Wed, 8 Jul 2026 09:25:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 60F886608E; Wed, 8 Jul 2026 11:25:08 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="q4Np5vmO"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D80B866074 for ; Wed, 8 Jul 2026 11:25:05 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0BD701AC5; Wed, 8 Jul 2026 11:24:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502656; bh=dhl5gblzCqtHc/mQBW5GOxetdk2axICofVb774mO1ss=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=q4Np5vmOkx7FYsleGiAQpDXJg3RZ1GHl/OADFVjehNADLpCIgz0BiEZgN0P+iMGcw QPdMFkt9283AzCTzaa2nBreuTiJisJmi/jhJmEm9IUziT756RTEjo4zhQ79jNy53SY o8/gQz3AqFXC1tNE4Y7ldW3u2ri7/JXLl0DZkEI8= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:49 +0200 Subject: [PATCH v4 02/35] ipa: mali-c55: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-2-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally X-Mailer: b4 0.14.3 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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdsWFkM2wWLrvyLkyigqojFmaXnG+ANOEKMw xYC8uB64qqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbAAKCRByNAaPFqFW PBmmEAC/TrsB6CLJhVQx7Kef5tlNP4/H3hgXgP1LST3lhqLOzz3MmXwndm8XeStWnRFnvV98AQ1 wDBnW7nW/M4fKRfTKIzXoxVD3PbgpY0mM/s/6kZ2jy7iUtEvVhfpIEyEWgEcx90NSrEl5eGXAAe YTTnCAfCI62Yo6o/SXpbCFIbRyYbRMbBKELUCNc1IX/Zfv6CAEN6uXTo3oCm3zuzVEHWs0W1lHX CoJMKufeY1LgfPxS7iPdkSO1zmpihnIyn5g9JlsWTCWSbz2OjiUoEqaAaV3jhS7KFteRCspOU1U AUCUlh+o2eG0Y3fUovToOnQ7mU1Ez2Ht1BxeUnFcXNMeVO2AHMRhpEeDzKH7oz5sIH7BTGwZioD WHD3yhhjooHi16Q6jUXEWWs3nZlLiUns5rRwLeqjzI+/kNUtoVNy1gBpv0Tj7gVpBSon/CTPyrY sU3zGLUP2Oudlg3Rba2uv8+wVdaIG/zETHCjBfNIRLcOTMcFc9cpzDMkPC7Z7xUlHhT0/twYy+x oAHF19O8mP8Vj0qnQnliB5Z18pteogrFdlv6jaF9fPkmaZ6+9OrMElJk5wOtPxxPZ91vtFB7JYE OpKk+AZmzytkZL54b7CvNhDKZYCMI6Zg/WVBOkOysIxsqmadIaMFDKA3vXlheYhGQeDyOAdSDEd TbauGq7lT36Cbjw== 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 Jul 8 09:24: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: 27268 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 82502C3301 for ; Wed, 8 Jul 2026 09:25:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 750E866092; Wed, 8 Jul 2026 11:25:10 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="B5wus+fU"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1045D66075 for ; Wed, 8 Jul 2026 11:25:06 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 595271BCF; Wed, 8 Jul 2026 11:24:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502656; bh=3svjqdoXwmlakcvPDT5k9mm2XutCpDjaQqs2FWSBezI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=B5wus+fU1ZlVJfpNqG3qEnHQAItTnHxySaC5ABQWjs6WLLUx09TrKahYu4l/1lpKT 4H5EUyd1yV6n08BHayuJA0Nkn0lzjwHFKt4Hfa5ib87/pHV7Xiw8KEOnNCTi9IF0Vp MdanX2QBcEn/dvcq0zHdilXUSGhUY464Y6zy1x9A= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:50 +0200 Subject: [PATCH v4 03/35] ipa: simple: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-3-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=13525; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=j/wvgjvzM5awvgBWBA9/i4UhFoQShYGtyfNRGzvMXVc=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThds09LEaYeV3AvDjvVs3SFTDlZeZnmMfFrER zdM7NEenrCJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbAAKCRByNAaPFqFW PFcCD/9CKXGdwY2U7BiBesaoWhRt72ReU49d9t3VqPhWh79TkicbTucVCVz9sWky3o92NZcadFw wC5BJvLW3HxOaVhNXKMjiKkeocJaX4xkwY/JWT4y1jKcB/swO/ZsJAEApLB1g6mAKil4YAXDdNX zkgr3lxho9hw1oGgSIDM63Pwle+oqYE38fP8jMgQrJVQfpNXZMz20b+zmBgotCXf3AVFY94hc8J Mo/2vwpd0FZDD90hmOv9xXxGjl/hv1pSoZZvA//eJZJ3TcxcuuViIhOaN+7051znRadxVGmKYug WGlc2HwTralrLZAvGzEaNBzhPYcoLoxhiPLnqrun5Tv62zmWr9xVKgxRrh+Qnc9395rFmayxAZL JN/krFil+daE+Qgu4lVJWZFnn+sMTvy08SVHl0avvzc02gebcQaODBmd0E8+Ep4zToe8oY5NlXB SVTUZXV9GsBNoE0ufwP8Mxqr6MNCwM8FBmxVDRArdmzVhDqys9NbA945qDDi+ovsSiaeLjFdwQB vm3Ww3UMMzW7tyXRzDgkp1VWG0IikbnXUgJ1G2bKRCHG04XTm6rA8AT3cxtvc7xZWVva+MBg4sc qWWKgEEqfmcozEErLOUUTxC+kCzN3+Ltru/2dvA9ixAbz+W3zizbHf8rDMhJL3U7dqcXdRXd7bP S/UOyxkVFA/46mQ== 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<4, 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 --- .../internal/software_isp/debayer_params.h | 4 +- src/ipa/simple/algorithms/awb.cpp | 131 +++++++++++++++------ src/ipa/simple/algorithms/awb.h | 29 +++++ 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, 141 insertions(+), 51 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..e8c965df65fa 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -15,7 +15,6 @@ #include #include "libipa/colours.h" -#include "simple/ipa_context.h" namespace libcamera { @@ -23,41 +22,98 @@ 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; + } + + 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; + } +}; + +/** + * \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 +123,37 @@ 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; + RGB rgbMeans = { { static_cast(sum.r() / nPixels), + static_cast(sum.g() / nPixels), + static_cast(sum.b() / nPixels) } }; + /* - * Calculate red and blue gains for AWB. - * Clamp max gain at 4.0, this also avoids 0 division. + * \todo Determine the minimum allowed thresholds from the mean + * but we currently have the sum - not the mean value! + * + * Currently set to SimpleAwbStats::minColourValue() = 0.2. */ - 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; + 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..fec48c6eea1c 100644 --- a/src/ipa/simple/algorithms/awb.h +++ b/src/ipa/simple/algorithms/awb.h @@ -7,19 +7,37 @@ #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, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -29,6 +47,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..ff37c718c6e4 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.temperatureK; /* 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 49382b4c2719..ab22635fdfaf 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -1010,9 +1010,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 || @@ -1025,7 +1025,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]); @@ -1045,8 +1045,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 Jul 8 09:24: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: 27269 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 7F05FC3301 for ; Wed, 8 Jul 2026 09:25:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6384F66093; Wed, 8 Jul 2026 11:25:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YNOdW4x2"; 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 5F84A65FEF for ; Wed, 8 Jul 2026 11:25:06 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A63781C2A; Wed, 8 Jul 2026 11:24:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502656; bh=f9+XfBVeEJ4VSD1bfCxQGr3LUyOFZa7nG5xmDQhXjKc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=YNOdW4x2n/Hn2tY0ySSIf5MvpKpkpXWcfUwT+oYnioSsmUOeWndOwqIWRovA50dn5 LUTWpFf2nOh29A4mDCriL5OFu8mrdug3cN7ce6v669W/hDypFv4iGX9oQJRtIX1+LX i/9LtBMZOusBQSRlRjPMEKJWinJOPbRQ8ZyAM51A= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:51 +0200 Subject: [PATCH v4 04/35] ipa: libipa: Add CcmAlgorithm to libipa MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-4-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=12018; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=f9+XfBVeEJ4VSD1bfCxQGr3LUyOFZa7nG5xmDQhXjKc=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdsxOoiKpbo7+Zyfl31TqJA66UM8u3cSM9I9 n+Ol9+aKemJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbAAKCRByNAaPFqFW POfED/46qVa6RGCPqy/zP9QlUhhixB1tD2z2+2U1dES5/PsvifUU/HCgQVlhIMlzVn4ipgQiq4l jTWyAZys5HFX8KjZEMGrzPgdSNd+dcLElrf0kfKSTlpaMpXa5r8o8c1hYmfu44lc43+ltZ2Cjrf s9DPFAdWdSBJ9ZWjcALadNr2XdjO2Tw0JvdvavsTN6L8Q23SPBQr4625wFfFMT/eLxBSADr3IMB qcZUZJUeodPHqk2Q+ozYdu/PMHQmVLI5cEGl7yfJMMEMWROkJ4L20gcaCxsKw+hKorEyVT8z1HQ tlvO36X0L2GfR9DIFLqxMMcd1kboD4PT5ocQD9euvolj5Q54PPUB5FBEJaz7eakSf7zXQphnuVh VZN0B5m0stanSbXFZu5f6SvRMY87ukcVcajHHxLMMTwhAEG4VHqCFhFY95yaHL7aMbXnCVNOZPH h7A7/jk3sNcrvwKimwlyrMlWBnQEvl5N7kA35Wczj+uwOcZJkAPHDjwMz0B5n1eYNeAeWqQSPnO KZ4ZVQJgrc2ljyHMSYFodm/TAL93iA1kJFixd9cUjDrsL05JhRzsLDsoy9F6Lkl5HnBTm6prA9D sSUP5kLjuDd61j6rRwsmYB7FalSai/sFPxDU79ZGsRscLe7o1oroayvkza2zkJYtAJzZQSGy9uT 8hCTobjH6lHQPuA== 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 963c5ee73063..edf8eabd8b78 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -7,6 +7,7 @@ libipa_headers = files([ 'awb_grey.h', 'awb.h', 'camera_sensor_helper.h', + 'ccm.h', 'colours.h', 'exposure_mode_helper.h', 'fc_queue.h', @@ -28,6 +29,7 @@ libipa_sources = files([ 'awb_grey.cpp', 'awb.cpp', 'camera_sensor_helper.cpp', + 'ccm.cpp', 'colours.cpp', 'exposure_mode_helper.cpp', 'fc_queue.cpp', From patchwork Wed Jul 8 09:24: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: 27270 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 4FF8EC3318 for ; Wed, 8 Jul 2026 09:25:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3DD0D660A8; Wed, 8 Jul 2026 11:25:13 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="q0AS1tQd"; 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 A4D9B66078 for ; Wed, 8 Jul 2026 11:25:06 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 060E41C4D; Wed, 8 Jul 2026 11:24:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502657; bh=ltIYhnnM1bUd815aMZ307m3Gl3PwVM2zbZHeIKk9o7s=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=q0AS1tQdsZMtYuLYPQdNqEuZFK/JkaymsZCN00mJd2qBIdaQmFEo9aegOAWydk9sk yNaUFehixkEF6/eVPm0G/luD6mft5re95rWe6YKiXfzhu/Zw8A8nZfMEIdVRH0c2WB OlieeMkYTg0LdbbNDHEb+pKBdksO72/b1UvDmxac= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:52 +0200 Subject: [PATCH v4 05/35] ipa: rkisp1: ccm: Port to use CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-5-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=8073; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ltIYhnnM1bUd815aMZ307m3Gl3PwVM2zbZHeIKk9o7s=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdsZ3M8Q+RpQgGHzDYtyZIsxgPIB0bKvDeCn 6zxUfThVEmJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbAAKCRByNAaPFqFW PF44D/4hUgpIO1hrJsL7JU6H8U/0oHMBu8mDos9HReKUP1jd18WODkdKVBaGG+fVEu2W65dPKHf bE8xazjgdisLUiDYv6v74j/zLQUwS/Fk0pqEVJdkhxJJhhc/HEZzZTK1uYwiZTMvaDrwjY7rpMT Z4DvAEc0h3cIPZk/GlxlWrgvR3cJp1kk/Kotwb3PtmxEkaLIO6JRFcRzYUGQ4f+bSh27EM//VfC OncaBSjXS5ZqUuBRaEz+agU9HGIdwWxJOEYzHTBGIhbgryLdGE4Ef+x+A+xeEMgzPnRGsyQyoKM 7UB8LGK9B3qInXx6SUFdoxJz2lqyxH0RzRxVShyaZKm+EQ4yBWyjFeIcV5pJVUD4w5arh3QHYa/ SjtYvgOvhPjfenHlJMJLEOq/M+CqyGjkfaNtUgSlilgD5K6qQU35hUm35qxpZGcs0lYCvE06P56 O1KT20rGTG6hWz7W+Y4BCdC7kAhnZO8Crb6/HLzeEePLwAhv7J7D3IMIZPMIEzAMniMHFzxnmfj mGHwK+Tu0DL1iIN93EUvNE9ydquV2sjI6uMMw0xCnyIDps3lnoSLHV05xQOpvLFuYrfxjLzaMIm XyuMegu5gugyYGVjbm9IGIQR9RFym1qxtCtjKbOspEuMU3Wad8zjCzXdyE7rWRijtwb6tI22XcB WsMfAoL0OM0sccA== 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 Jul 8 09:24: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: 27271 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 0A6DBC3301 for ; Wed, 8 Jul 2026 09:25:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BC8F3660A1; Wed, 8 Jul 2026 11:25:14 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="j2hvPUlW"; 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 1D69366074 for ; Wed, 8 Jul 2026 11:25:07 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5155A1C94; Wed, 8 Jul 2026 11:24:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502657; bh=O5np5iCaqcColGy1ScCLHBtBFdhf2JLWz64jfbhuWEU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=j2hvPUlWbW3PEuDS11Znsm9fa1wp0pysD2BQAg4mexNSvzck69VEIzs5ntQtq1aP8 lM+QNpmdNlI7pY6V3J8GwQwMKl038iZWHfwf2GpboxTtxmUMywnjIwOMvPt4rTaHV9 Xkp/qE6Q9t173Ezz9x0au1G4idVcwM6MAzZJ+kic= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:53 +0200 Subject: [PATCH v4 06/35] ipa: simple: Use libipa CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-6-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Robert Mader X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=6146; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=Q6ROx4RoTyRE9qwF5Bxl+z2g1LZCgklmTjdM2u21m0E=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdsQl2JqKEJ8Gk7UEeQssHSSsYuW9Ip99mXL fq0tpTNCf2JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbAAKCRByNAaPFqFW PL5UEAC3WYz6kNvthDdBQbWxmzfoqq+xbZlvnAgHG6W9c2p3yys99ZzLofHxs5Yl7cj33cmj2YW 7DmkNFp/GTUGqyXOwY/ygR+FXdA66jF2MjV9V2vqb6hlUo9X8VS+ir53afQdyCL6SSq+GdUOZxg slfHEzGXsCjJzuYGXUNugavL/bJzw5FdY/ajrFrKUpez1fYRyCSJ2FCiHJVdn7V6J+3bRyiJHJm KZumVf/umCTNUZU5XhjGz739FGetoBzWGtgodONeBPOudChD8gtwrw+dntD20VAwdQ5MMb/KF0P PC+rglaWUENi9PLjEWuWkgg/BAYicU+pJzML+UbegK4VSmI6WsEruQFU6hsKC2uojRvFM6azg0I 1OFTRKXUpnP7L2orkuUwBU9R4JG18qvYR15TTCEkhprUqxmCRs8rtrvlTs9+ZDZZS3cUcIYVECa MepSd8vV7JiS6Wb9CuYf6tmmLApm41lM5m6Byqkz0IVCT/1OnpouKRVvIHHeheDnZryTB8aHfvS TiWxeLxkpkXIOVepcf5s7kzSIWVVSgzt+AukUgW8OxX4VesXi3Rb1qzxk+5E4zauF5UQ2/+C6JF MdysjwLFdj+GZhCoAPFje/26sPjiOdgA6X0l5f5Vyuy4sdnKe8OH1LBVMxeT5mUbXsQeAWIodR9 zR9GFernHkCcocA== 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 --- src/ipa/simple/algorithms/ccm.cpp | 72 ++++++++++++++++++++++----------------- src/ipa/simple/algorithms/ccm.h | 23 ++++++++----- src/ipa/simple/ipa_context.h | 5 +-- 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index ff37c718c6e4..e0cbd453e32f 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) +/** + * \copydoc libcamera::ipa::Algorithm::init + */ int Ccm::init([[maybe_unused]] 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) { - const unsigned int ct = frameContext.awb.temperatureK; + return ccmAlgo_.configure(context.activeState.ccm, + context.activeState.awb.automatic.temperatureK); +} - /* Change CCM only on bigger temperature changes. */ - if (!currentCcm_ || - utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) { - currentCcm_ = ccm_.getInterpolated(ct); - lastCt_ = ct; - } +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +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::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) +{ + if (frameContext.awb.autoEnabled) + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, + frame, frameContext.awb.temperatureK); + + /* + * \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..0d35347ea583 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,15 @@ 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, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, @@ -36,9 +43,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 Jul 8 09:24: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: 27272 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 2DE9BC3318 for ; Wed, 8 Jul 2026 09:25:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9FB296608F; Wed, 8 Jul 2026 11:25:15 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WLrIYICb"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 531CE6607D for ; Wed, 8 Jul 2026 11:25:07 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9CEAB1CDC; Wed, 8 Jul 2026 11:24:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502657; bh=4A9kajOFuKXSQXUsoFZ+WfGwjzpqM6yrrwEjlszpxEw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=WLrIYICbam1cWru2aQtZMn0biqnY/pXB15qn88klN6BA+TGBgDjQ64q6mlU75zI1M ii5k8XcKhIwEqrIqBHuSQrLDrZk3KSStk3ZTAAS8+rBYE+/2/ouoGdGywAki7fmNW0 kFTr2EZZkIsOJZz1iV/jfq/KMjy0M7A8tw6NJRuU= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:54 +0200 Subject: [PATCH v4 07/35] include: linux: mali-c55: Update to support CCM and Gamma MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-7-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Daniel Scally , Kieran Bingham X-Mailer: b4 0.14.3 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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdspFyKv39w1IiFlyhE1r1Meyll1nr/b78aW sxmPvJ6bruJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbAAKCRByNAaPFqFW PJs2D/9uqzf7d0dndZiso5Yw7maD1cGPQdwaAKhmmZS/vfBoZi6w6EqtlAvfWeIwMYzUFYLBfw9 oELr9KeojNDdUvJdRBQLPX07AVLJHX/FuvZmvT6S4/Z0aDQVsFmdx9jlcZsAPxQe8FocL4wvskm 6HSUH6tvrzpOeR6lA+vURPdwr5e/MQxIW4Oja8in8tMB2mK10+YGPnyfJ41t0Z0WN1RQIHkvN20 g6Lfo+7po/jAuN0PaPeBm8oXZ1QAt4u7jsYa1EC3pT8j36VUhoiQL7vmteIooJgMKGWJfUiyEng RCLfwsyXxnnK8qIbH4/mtXlaY4utbcSfbZJD0ExpMCnsIIKOlPKEA8p2Snw0Bm5A4tStPTEm4M0 w0A3dGVYMtb4+XSEa7H+15QGSFXozoyL80xMp8FcK2uroPBYxlJEUmfQC3cQuF8pFNevW+iM8zp zHJCPlj64z75DPypJxPyAZPcwQn+AixR5UWNGdk2b/90K7ItSf9T2ibOuMIZNOm31kmtT4SJJ26 jcXA7wMZwBYw3sj8z251iT5sUNwoLONHgvjfSit4goQ1wrKpad5ohcTsPIBRqzwHWl+TDsiOeET di48C2a/j2wi/R11+joLWOHN6+fihGC9PWrlkZGZEmQTxRoeY8EaMctzyo/pjdkGRl5s/AI2iGl 3+rOOpo1yQ2u/aA== 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 Jul 8 09:24: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: 27273 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 71D5BC3301 for ; Wed, 8 Jul 2026 09:25:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2BF47660B8; Wed, 8 Jul 2026 11:25:17 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QXWSk1sf"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8D59766082 for ; Wed, 8 Jul 2026 11:25:07 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EFAA51CFD; Wed, 8 Jul 2026 11:24:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502658; bh=bRL+cvadKBepGDYjRs3g72dQEyqODsUmc0jD0kXxbRo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=QXWSk1sfSmJGs8rPHuyTOu7AjqDbGqQvci0L6zr/KEnYbWUdL/KSH36LJmw6AZaAw j0D+fL71Y8mfixJTK1skd0V1ZbvQcFPf85lvyeIOFQuwp6m9zrYhsTEClX192Jt92y jZwcP5OHv1zGpF19khDErE0kGatjA3JbeLU+PvYU= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:55 +0200 Subject: [PATCH v4 08/35] ipa: mali-c55: Implement Ccm algorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-8-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=9899; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=bRL+cvadKBepGDYjRs3g72dQEyqODsUmc0jD0kXxbRo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdtwQB9Ly1FcAgwW4pFt3ydJXq9qR1m3Gszj /AJj4NL7mKJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbQAKCRByNAaPFqFW PNiND/9Gx4mHR/KY6jIfbkXOu/O33sNWJnJi5lRD8ucwOUM+QL0vJR6wF0BsQ4nnJKOSTA9SytV fwPgAXHUo5c6yx56Ga0pT91REhDiGqoUFa1PTpKQOAuQMIB2dEYJLVqxEn1FrHHIHgkIfLS87Z3 VDJjQwf3a7y56pV9XmSRReF9QyEpSG83u6UrLOiBPdyuM3v4e4nXe9MN7YF93d2Vn2xq9whRfGi Bitv86W/Kq+CmITW0PdGUTqh9QmeEfPpEnn6dJDTnmloREaBCFdc7arQmc4hzj6C5jeg9uYGplM H2TDD8K27LlmCqpCl002+5y4gZQ57T64XKEYeRpkPaWfKvRY6dGtj3edRXMGUgQMHSV/SsNZE/7 Y8Y6kVUbHhUcKCJ5V0ln8PcullSMg8Z9Ek+AhmAI7qgcT+JdlaLZAx0MZu7jvH4wpNDtSXI+Qcv lyDI0ByFCxX3ydo0nakSuQxiORr03PPRjnmiGTzUYJ6VYE6CIzR+9HFlQ3BeiFdiABjWGBQoF2f uKX6xFmhBFmBbzjLgZzAP63i3wdge8ksU5ZubGDW6eh/he1mmNmHIRIvIp7njllcTvgKpbz6wVi Zi7TRZeZ5LB71+L5Yo6K6XxuhZBa7L9GUUiZl9Rse4ewgDNkTHB2qOJILiXaPDx3jy2GQyRzhL0 PUTRjp4UJreC0hQ== 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 --- 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 Jul 8 09:24: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: 27274 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 8B816C3318 for ; Wed, 8 Jul 2026 09:25:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 741C666073; Wed, 8 Jul 2026 11:25:18 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="H/17L4lb"; 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 DAC5C66075 for ; Wed, 8 Jul 2026 11:25:07 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3A0741D03; Wed, 8 Jul 2026 11:24:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502658; bh=/YB+sHNbmPImz60FaygKFvKtjE2RV5KVOUMkfOU9+mY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=H/17L4lbhaA1Tc+RBWkRdqf9WdLk7EyI+9Ff2E6t0uVZP0RV+z+Hs+Aedj/qEtIkF ybkP5Yemivr/nXeXaC2x/5IzGpmqcIET2Ry1FzdKFwjJpM0aTTdBzebNGLBWWlNeDL 54wXdFIOcv0IRI0WmV7UR7WxTVajT9fW1bcJCk7s= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:56 +0200 Subject: [PATCH v4 09/35] ipa: rkisp1: lsc: Re-architecture Lsc support MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-9-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=15591; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=/YB+sHNbmPImz60FaygKFvKtjE2RV5KVOUMkfOU9+mY=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdt81hUOpush5EbJ/Cu9JU0oYLXKJemdMZeM wD3zDvKogOJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbQAKCRByNAaPFqFW PBl9D/97pPP+grLwcJL2RDgzf7pkRcbMKrfVDn4swWdW+jG1o7++S1rdgIEgSqrAqL44i/eaTa8 oYaEU7B5Q5xenoSK9Hco4bNnTwsHXjjWbjWuS7g9NZZsdyM3U9P5kOJqKqWiic6jU2vppAjOP6y KlNaIJ2ustmCAi0zP89fuW9FvAOIXsEHuq7onNONo0RaVTx395A/0ihuwlKuNPCx0YvOjlOqOQe ETQ8gSMiT1l53+B5OGkywR/oj9J0OUn2bnBuoYODhH7vPUer3++k84ORYeJMy/pNgF7Js6dpAJ/ +P7IxZvftlr2UB7Ft3i/AovzmSbWEayTC2D6zasJCX3TeFOYHjhol4np9qTDuztKqyYQJhlp5GI +qGBEB2BEDp5pNdeouOeVJm7TPft14sK06PnLtuO0rZAkHfkMDv4VMf//Y+XiUbHNPDaw8phvoT xbTNHxjmHZ26rDGl+jBsSxC6JT2gFppwGt9VlpWX7rk1OIFyZ1audgifFpfahr2MWRGBY0zwpJd Cc6v5XqHgLLyUpSSgSbXzwI9QJ/C5kZ2pAR8m1q92s5MoSiIT+t8CPKiqESl2huEgoixwCfR4ah /JhBgefSLDwbjCFOc7ard1HkcuHnYbx71w0JkLmqOREGX4t/M8wEUPXj5sNpeTtC/sHELV610VW jieHkYWNipcO0WQ== 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 --- 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 Jul 8 09:24: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: 27275 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 9D2FFC331D for ; Wed, 8 Jul 2026 09:25:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D6B3166091; Wed, 8 Jul 2026 11:25:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="lZWVtH1G"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2185066086 for ; Wed, 8 Jul 2026 11:25:08 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 81BF41D18; Wed, 8 Jul 2026 11:24:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502658; bh=hiz0uT3/3AsxSoMkNhh5IRTSxtldTuT51MbBXVsOjgQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=lZWVtH1G/9EN2sqSUDhR8RHtmlOKarzcOd6ssrHxPIBuNZW+YDHkvcfw9aeugqXyY tMM+4gnDLnD+lrfdc+ZBQmqO/Kq2+JVMLC4YPudZJd3e6UW6+b7VAzfg4IdbQNYPNk rop4HtZQkiEATfMSap4dPKtrQL+dG9WCdBsNQPB8= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:57 +0200 Subject: [PATCH v4 10/35] ipa: libipa: Introduce lsc_base.h MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-10-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=11124; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=hiz0uT3/3AsxSoMkNhh5IRTSxtldTuT51MbBXVsOjgQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdtUf+m99g2j3lF6pSVkW0kaHsV59IRafNjK hEFIAAqeFyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbQAKCRByNAaPFqFW PB7jD/9mwxdcIVtCer1Z3BM7Ucy55V3qZ86wm+wycyTshM3wsUYwYTEhUDJO4RzI1IQcP1/SNB6 g0dWTFeFOLDwJK4/3PF4jrvBYWLIQy1mMCGHA/3Gq00QrYCqqQHKSwnoFDuBXyMvwfXcD4sk5Sh yhptm+1PBnItj2U4NRGq9oAbdeR09n+DDAMXWf0LUfBxITCC5FS4LStneWCYkcbzjCHDFG6Qb9G Is5gaLuFriq6FTe/PR35KVjqJlobhQW0uHPbs92L3qIwTz8Sb24hx4VQOWJV+SqyGcw5rCNDvMj R9t0uJ8N83VPtRu+dEAXO7w3X2dXeY5bssPcT4a1Z7V64Lc41+hVlASXXrBPNZG6OVycEYqEPmk 4UjYZRlWUb6rVU8yVqKUJ1zNC9vT9pBieKO9qdWaPbTfYuKw8O7E5xEKGFngF73psFBptEcwoWq MHGs03KP3yKM7/qLSzGxwD/Id/gvFKu4EOYRXj6axvrHM84F9BrZhp92I/4Tzv+7jOy6t2z/oCZ HUeKKee02uvTf5sDUPF7nZTJLOkQNWQBxnztar4nfk4QM7RGcZlkEvkaBqyfsHwMpzoV8IYnPoL qJVJ81Zsvs/tciM9dnelVlSs994ORI7NQnSeQZZLH5ABctrN8XmC7Jbpn6eEBMo4bsbBZj2AgCP iYb4A152nme/P6g== 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" Group in an header file part of libipa the definition of the lsc Components and the LscImplementation interface definition. Signed-off-by: Jacopo Mondi --- 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..d05075b988d7 --- /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 algorithms 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 a 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 light + * 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. 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. + */ + +} /* 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..93f819d3c2af --- /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 algorithms 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 edf8eabd8b78..96611e90ee1e 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', @@ -36,6 +37,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 Jul 8 09:24: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: 27276 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 B5036C3301 for ; Wed, 8 Jul 2026 09:25:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D3549660B7; Wed, 8 Jul 2026 11:25:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TlRqLiFW"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7EFF96608F for ; Wed, 8 Jul 2026 11:25:08 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C1D5F1D27; Wed, 8 Jul 2026 11:24:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502658; bh=rYGOyJqlg6KGcH57Gl0nXxGuPBsSavnxemzwwosWMBY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=TlRqLiFWgWYiWQ+o/7ETV/dMYO7FUikq0uve9FALG1pHGDQA0XDr8m2hKapuxWRCK +edz+peWTDeOh+Iz9S7Z0vEpLXOtEhbDRzbvZuCdwvyhLVTzatPKKrQZQr0OQmsbw7 +jrwrcer77V8WFtOsey4+O9icODMN1+qox8TDR+g= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:58 +0200 Subject: [PATCH v4 11/35] ipa: libipa: lsc_polynomial: Don't inline ValueNode accessor MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-11-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Laurent Pinchart , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=2952; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=BEf1W1AyI5E5rj0wvDTXSlHFS7EeY8EQyX6MdqC3noI=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdtS6mkGIhgjjlkvAyPgbi6XgJ8eSKAs7JRs Vuhvx8yjhiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbQAKCRByNAaPFqFW PJhKD/0TGZPWugWM8278SR+XI2eh6mAkSHzU5Iy4ZoGje3NTWKji6VLdm45UNkjotARtz+IvS59 yYRwVqnUivJ4+XUPavwwFKVAYhxp5KLuf1g/sq9eKwDoWU/WQvoex/mibZ2Y2RMFriXTXcocFJY fEkww0MPO3esfy57DxX3oU+z/fgG1bKyEf5f0SjvziCS+/7sytB60HaZaU85vtAXqqyUgxFeV9C PvBPAAWEloRPRaEgLkqkVRyakwZpwcTkQOlphc+B2UAGYiie102E6sFlpHpdSjPb3cIIlEXgLyM 7z2kxHlscFc0VMFhfkCW2UEYUM7D+R7JnfMubetsfd/iYHsogogV3pz30uroxSM73/hqjr7wceb e3ShG7CdAEhBe6/FDxVSgraNekmXgqmm1jJx5mGU4f0q9eqe7SxdmX9WdsHv4j5WyAFUmsRPc0z sYgAAvpv+B5IXL8+Q4LFvE4O6s/DEe+3uCToxpHxJQA69n5Fol5O5wRR30CcNjviQGy5bum81pC b2sfDnkaXoKbJ0mBGUT0POWjpVIAzHop6bAjr4elHXA+BhUN8sBaK2JCHCTVZjg2QCnFAnXbf1E XdzpaiuOK1/+LulXmGJ9Ov+LIaxJSyYojuv78Omd9Ky7jkmgt24UyUAYsU76C/aT3wyVuQKZykr 1FCdv5lim/nQoAQ== 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 | 26 ++++++++++++++++++++++++-- src/ipa/libipa/lsc_polynomial.h | 24 ------------------------ 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index f607d86c54c3..00569c2fd160 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -77,5 +77,27 @@ 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 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 d7d9ae42e360..d52ec86946b1 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -80,28 +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 ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); - } -}; - -#endif - } /* namespace libcamera */ From patchwork Wed Jul 8 09:24: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: 27277 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 8BE02C3323 for ; Wed, 8 Jul 2026 09:25:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C1F97660B4; Wed, 8 Jul 2026 11:25:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NSbmZKhG"; 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 CC80966091 for ; Wed, 8 Jul 2026 11:25:08 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 260321E3A; Wed, 8 Jul 2026 11:24:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502659; bh=4MG+POwC/aVnb/p3CviZ1WmmE7s70A3EWk1sw1voLdQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=NSbmZKhGdX3LfzOgtUBxHVHL6/wcGNRVDkd3uP4oM+mXB/5MITqI8vbMSJy8kyS3C Tkzd0mZmhhGaNvdBAdbx90HvofqsJeLfRpNRGz56e2sO/bVJXD4NXZBh+lKtD5LkRV ovbCtxIpHKMu4EbBnSQOopIbdLufxmURWh3ARGKg= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:24:59 +0200 Subject: [PATCH v4 12/35] ipa: libipa: lsc_polynomial: Rename LscPolynomial MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-12-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=6293; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=4MG+POwC/aVnb/p3CviZ1WmmE7s70A3EWk1sw1voLdQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdtgxIfnGWmlYHIDflINNLD5k7tD0YiE36p4 fP6UeUPxLGJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbQAKCRByNAaPFqFW PAcUD/9KmphozutD8+0cqGRT7H+8YgCuCOiZoWDDRWUXZIw+0jHPiCUWMg0bb7YUW3StiD1iMtn 3WQ7d2K3MlcMblj8vyJ1hl8CP8B81Fz2Y40LDUw7ot5VuL+lOXR5sJBs7UxG/0x5VQsHXSv3Ls+ m38MVhICYAY9mY/4JwroMR/5B18RHO0j1g639BCmXOWjigHI+CtEHRJetWSEP4zdaMBqUYJF/fX NfmlGc6eU0A0VXCDzSJA4x7U/wNhzs2xivIsqYMrPCB+u3hZJL/v9pQGOeNTeilXIHH9s+XcVPe Z6NKoO8JsGerSOmQNAZispVwLAMorQk3r8Opwb3+vy681w2vDt+DcZonhiUO2C0X2VeNpRcEUe7 3S5o7Nj3swQPAXvn3LpdZDiioCD/qeeFJoRKYyiyNzM/xi/3XmkKGEaJ6gQpjorQHPYMAaEWLSN n0T/5odSv8e2H1VyTYHQMwIMYWxTRMbbAG/6ihnm/FJyU2lcDPRTzlHVQRH/9dqLDERf58p/LGj roS+0DXLLZTkQ92QW6xFg3ISTe4gh5zzyp2Y0Okj8DuPyNdAyIObN1N7al+vtDHTgUU9nOGp5wL mWjnX1lkvZkB2el3ju/aopUjDIz8rZYA2GbbNdaEqr9nMMQIaQAqFdIni+E4diA5nCS96/WjH8s FjRQhd+88OS5/yw== 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 --- 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 00569c2fd160..d8981bd8a26a 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(); @@ -96,7 +100,7 @@ ValueNode::Accessor::get(const ValueNode &obj) const LOG(LscPolynomial, Error) << "Polynomial is missing a parameter"; - 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 Jul 8 09:25: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: 27278 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 563B5C3318 for ; Wed, 8 Jul 2026 09:25:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CC6DE66093; Wed, 8 Jul 2026 11:25:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="epgyJxfK"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1699466073 for ; Wed, 8 Jul 2026 11:25:09 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 700711F14; Wed, 8 Jul 2026 11:24:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502659; bh=7XkAt6FRI8LhqnI83clJrAZRF1EQa6l6JYmohhS5970=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=epgyJxfKPUlpG6kBddSOLkWRfAR/FfQyXYpHoYfwnyY0ZGl4kSD4aNbtvUdKEcoQe l9St7QOxVkSEH2V0CleZuNhuugCBGltYxZRZ+tPVG39IGznPl241Qry27U5+tXnyjm ee2dCWUlClzw5xQJpYg82bbuTkIVWuS9wnjJ+onw= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:00 +0200 Subject: [PATCH v4 13/35] ipa: libipa: lsc_polynomial: Do not inline functions MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-13-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=4030; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=7XkAt6FRI8LhqnI83clJrAZRF1EQa6l6JYmohhS5970=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdtKrRz5y+XiD+z2U7x4axhch7uvDYiov8ib E6rAKaMAWSJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbQAKCRByNAaPFqFW PJ7HEAC0+x+dgWTtd/y1SnVuBLJP/uPCVVEy6o68E7YsyOkgaDa1SS0NeKOHPrf8iEa45gw0nf3 JPVmwEkQoa6ce50LK9LLW+cdGazp3lC63AmotmShYjPRcAW6K7h8CRBLnxuTp5EZuL8jQiY5ymY 95sBDj5Kb66PkeLb+1gYUo0p7Ic84KRY6cfHSoUwHUrgjJhZeUTcyidY5UlBkmdlXNQN4HH/lfy 8x5lDiivYBmv7uhcnxl08sB2Y4div5o9Frkee0qmnq2nQo3CDyI75adRZGX5JQ+Nji7uXsog2FX xEnotFbKv7aFdAiLoKFiOuBAEBbCkTT011Iy4OTGLLxfExyiThV9bkAtoGPt0t4YRqgWZcI8bkg Nc+k6sRsGJ9AcBKJvaXYedLH4x4R245PQYzrBDi7va4HnoeA1Ok4NaqEJbL+wCRLfs0hNpXCilR 3tfse4SJs6GKQaRFALxgqWc+uOsmWjTBbjwzLsoTGeaxSAjuOGeeFIpd9WmtSn6wVuw6vIbGdpE kPestP5hdKVqEUZNkQggH4wEjBBkmu15CTIlgHDIrvtjIkmzAQHOOk93xv+Dt2WCZNk9P0HM2kl r3+tH8wXZyy9RHjzKJXGPyJMnK0t10iN2H7FLInlneNDBREQoLyb2N2zBbs/DpRz6rDXB+a8wo0 58bW7tGwNcITx9g== 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 --- 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 d8981bd8a26a..a845d8b1783b 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 Jul 8 09:25: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: 27279 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 4429DC3329 for ; Wed, 8 Jul 2026 09:25:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F3986660C3; Wed, 8 Jul 2026 11:25:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ghAdIkFq"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 60E036607E for ; Wed, 8 Jul 2026 11:25:09 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B772F28CB; Wed, 8 Jul 2026 11:24:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502659; bh=FI6u9HiVyZE3mGOS+nR17xOPN1xl6wtQAM1eP5szdPw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ghAdIkFq1nzSSpvEJX57tCiyj04ECMad7+t4NebjPqWE0toFyRRjV1r9U3Fy2WlR/ w/tRsweUV+aF/aPtdI/NKYOUWP123gISEMF6v6y+MI1W2Ru4s3VeHLiNxf/QufYI5z MnctH9q+jqtZuPSxnnhpic+gOUQmnAEYQm6xodic= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:01 +0200 Subject: [PATCH v4 14/35] ipa: libipa: lsc: Move Interpolator to lsc_base MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-14-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3329; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=FI6u9HiVyZE3mGOS+nR17xOPN1xl6wtQAM1eP5szdPw=; b=kA0DAAoBcjQGjxahVjwByyZiAGpOF22ilSxmaZQgSqc4otvR1kiuD2c+i8f/p4LKxfZXfgYpa okCMwQAAQoAHRYhBLXEPUiAmiCKj1g4wHI0Bo8WoVY8BQJqThdtAAoJEHI0Bo8WoVY8s0kP/jzw CcAyOb93c1Qo1iPtB7o+qoNJkw+wH22hOA/g89PkqLCUcLlBjMukNDymwF5YFmI4oaKpvyQVbRX Ywh3w2sQxuGpbNANxo7TbwR4AYww4r8Ydn9hwkr7OHgws7AgZQclF7VGdHrdKy3OQEAsJ4X9t/M MEwOs/kc1JjAUr2qvt0Rs5duHWJ/tJgyEGb9XGg2NUEi7aZ1184YUvsmzV25bXlcY7tWc9EH4fz GqyYiC7jXJAUwwQW2VD1irdlBbGML6dJradRmCJNCQRdWPY7tutKZaVEd8bXkeGFRfxtVewAhmS BtYNQ7rrCK4bYp2oOAx4tVk5keIj7pk3h/KEZl7GgBF0PB7HN8UjrloX6A3LMKryrHYTH8Ju2U2 A0VKa6ubw9K1j+hlzYlgiPQqc1bxuY5dgeSvD3totRIHkOXAOp68SsfpbzH0TFvU18vFX/Y8KHz DsvGTDw8oVev4lmG55w3Y3HHeOtaffDTCXmaSHvi6fVesiD9/NfexmncmvR+TKr73aKgogiAYBs qtQE0U9itUD++vaiGXRRwXnM83M756sSgo9yLVpekLVu//FuFOTJk6aFrdSENxu7aeMx1v8t92B zUuqnLxxTnypCV75MgU5i9CruGaCVT/dKBzXXvegTJQ5b/neDUF7FozzHvbKvxYecIJMLXdUwiI A97sU 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 --- 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 d05075b988d7..945020794b57 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 93f819d3c2af..51cc35888209 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 Jul 8 09:25: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: 27280 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 20534C331D for ; Wed, 8 Jul 2026 09:25:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5F732660B1; Wed, 8 Jul 2026 11:25:25 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WK8gCqCX"; 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 A4ED766089 for ; Wed, 8 Jul 2026 11:25:09 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 08C9A3F8B; Wed, 8 Jul 2026 11:24:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502660; bh=paWyK9DQsOR+SgPmSSZW4pV2/QQ3ktSeNE457DY+JaU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=WK8gCqCXej1fGQozra2qhvMLnvRQ13KHNnj3aSEUzj4OgoW57lNGExib4tW6kDl0J StbCse151yR4dzwsdYy5ets0jzwHl0YnyNy3XD6g/HuI9Hd6aGDgPshAHP4e5o+NiJ DSJY6VJAFYHlq9OARv9v3vT6hY8luko8rC9Wuqh4= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:02 +0200 Subject: [PATCH v4 15/35] ipa: rkisp1: lsc: Remove rogue empty line MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-15-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=601; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=paWyK9DQsOR+SgPmSSZW4pV2/QQ3ktSeNE457DY+JaU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdtHIxDF3IRlFuO+pVHeYxe9DI1VFoepSQU6 cas+Gl4D3OJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbQAKCRByNAaPFqFW PPL0EACkI9vnqw588kKU3U54aQecaXhy0tW6O6GValFTX3z8kFT0tkIY/R+Ody724XCzW1xjGqB ED0x5kE6NkkR5mUIYBCDU8JWL70vA13/5JTa5U7Zf8i8hYNerzNVhNhZCSMeo/E4me2KSpYdM5v 2w4VstqTKAVqltfNQkvdh0iDeiaH74//+bj+aBfX8TH6zMQXM4YeHkIg98MBfHcVvEeumssRNyE XHNZwnEJCW8/TJf39gWYg80NuJx2cym4RFhAd0U6yI32mNQNQtBuSjeTzZKbe1oGVpChyPDTzji H9RaJvk5eLOGyaNnUSABBIs7ykmpi+6PR6h8509dfXZiTi8SjoyBH/viW61xsJJSiOZbV4TTXLa jcJWz15o0umBcilWGiB5HJ/DNnoEmjvBXAaZlObDuUFTCY5nACx/JLD2vjVTUvujfseD7xLBaA7 p7zZ5sFz3quGsohFXWoi1PLN4i1ldtVfZXjdWQup0L8h5YeKHXR15n27cQ4RkEDPVSicIeTtRw0 uN7B+hmtV7E+K5pb4D5t1P7YwjOTcTD9WMVQ1tFaVJOyyYhN/A+2j1WyNZG7q1UcGpvPPs+53lD F6SFQXZY3nGTuS1cX4RUDQ3WoQcbDiGJQ5N+AScc0GAr5tnBC4i6lnn4X2j4xDlMZiqlhDWxRVc kcE1TEVMKjBU/YA== 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 --- 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 Jul 8 09:25: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: 27281 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 13664C3330 for ; Wed, 8 Jul 2026 09:25:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5FCD766094; Wed, 8 Jul 2026 11:25:26 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="otkLKJNe"; 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 EB40765FEF for ; Wed, 8 Jul 2026 11:25:09 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4A1C63F97; Wed, 8 Jul 2026 11:24:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502660; bh=wCqPjILm+lPOCaD99y9cPOoEAOZKACLZk96y9Sm+O2A=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=otkLKJNeo8fGIyKhAZ+tHEWtYMUNqw8TrBrvp4B9dZR6e2wOKo9FNuwty1hwuh4oN bSHOXPjGo2eF3L6gEtKjQ2gkc0XrBgxpWfXDRz9SZdOPfOmTf6vP8JJIeKag6W3p1h Ib0SMt5tgOtxHP7NDW3DwwbpDNqjaqnb0REdX3lM= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:03 +0200 Subject: [PATCH v4 16/35] ipa: libipa: lsc_polynomial: Move LscPolynomial from RkISP1 MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-16-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=14483; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=wCqPjILm+lPOCaD99y9cPOoEAOZKACLZk96y9Sm+O2A=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdumlKyyvFwFhaQ8ebd8R7G8k10vJ+XtCZ/H sL91ccSBAqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbgAKCRByNAaPFqFW PMK4D/sG2tRFqpatKRS1Ze0A2GaAofWWvzHUofm7ALgODeRlGUsar3E9yF/VwtE2oO3RuVda76Z uRJOYwWvkLKSBHtLnugyN2vRtpMQTcOSDjziGD4HHTv9fJ+pRkanZle6BPMODM8kKpJLK1qui3+ 3NffjaT60F3scNqMaiI2tE+cjSQuaZE2YLQGat4xVJpIrXSsgTOORR6DbbIgAAMSD4RqXyauGAS maGrO80A9aJf9QB3GeIcfy5+kTW9PL13GWyDVYrmpBjiHGC/baxQgmohahDSGZ3U7xeSX1xsLcC mZXZ7GCMvzIwZh7qeMxBYsdkt7Ann2ArAgVGK/NtPQQGFY9ujugJ5Lczz3p980vwjomwo2xefcz 7oxiyULyoTCe8BGPRJBTMdRym8Y2vGiSbCQSpkbM8jpj/9nmf/G7wvDWYO3aUw8tQ6jRmk3pqZT eWKf2TYlx4dqc39xOqmPHP/ffL2VU2vWSigmGrRlwhB0MX08+J5mrdt8WzinJrhwm3WyGYRYdaK 3XZIYomPgBX2hd/c73nArkEOVd4bMPghX7zqHhgGR9AZQNTs+TZ0H7ThhAuRJjBO4M2BuSxZCJq jMBQ0ZO5mkuAiBMEiepy7ty/oq9GJtu6rDY3w+PZIGDHYQKplwy+FoxN+yJuCGcp3NmfE4qVn21 tvh2iB3OIqxjeDg== 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 --- 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 a845d8b1783b..6da6dfdea9d4 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 Jul 8 09:25: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: 27282 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 E8769C3301 for ; Wed, 8 Jul 2026 09:25:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5E30F660D4; Wed, 8 Jul 2026 11:25:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ThCHzeJp"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 843E466098 for ; Wed, 8 Jul 2026 11:25:10 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D6B543FA9; Wed, 8 Jul 2026 11:24:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502661; bh=Xx5zJIB1zkJdCGevazKOxH+Pfpu5Y2Nin5HQC5SwJro=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ThCHzeJpEmF+8qQ8XSl2uCQL4lkQ0JJW56p2mbO69W0nH0AfYpv4xJf9GxhfljFwa nYdkQiIz0vXWgdJeqTgfzsELlfRHCeqAgekd3GrC4FLxdaRWmKl+uhPfX0rGR+2Sy/ W4mIHdzz90ZSJbENMrSGW92VMBUAyINxszYpAtCw= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:04 +0200 Subject: [PATCH v4 17/35] ipa: libipa: lsc_table: Move LscTable from RkISP1 MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-17-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=8313; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=Xx5zJIB1zkJdCGevazKOxH+Pfpu5Y2Nin5HQC5SwJro=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThduqIbkb0U3Vr5oNmGseVXHF+68C13ByJi+F kIEP9ccmTqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbgAKCRByNAaPFqFW PIaWEACl1jutFdnoDXNaGSAITKlrzw84E1nkOKbWPN5oqk1tHq53cbdsiyBoJFyTb9j38ZawqZs UJMyWjYnxzmLWl3X6E5jFaIHVurnUWI0XrUVpv3rigu34HgD38HS1PdSv+VVUCFFQ+ztujR+P/J xc40L+kwTIf9TKrMi4wSLGCYfhCMK+SL2amZ9dv/B2nWCE7IvGRWreMKObFKFQhTPpe+tYietzk yVHXhwu/PArifXEDAPrgWI289MremB0cKwX/FB1ZcYkyaP/dJB74LKNHBWxQ8FE9plTi5uTL9PT TYUrRj6/BSyNncjX8mvbqnc00RQlNrO7Qarn6M+pmFuxQcEQL/BAt8Fr9283Ccrq6CE+eYm/Xf+ pA7x4OgmfDdpuUYHWW9cCnsyJFb9A4MxJI0divG07tpi48O0CZo/9KcgI7GrqaLUwvATzynzWa2 xiJX3cBn+Y/xS5TRtFVjvfnuSo+LRgmTjaPvc96VXxr6dxpKOojjp2E6F4kTrk6abD1+nb9dSh6 RVP0MKa2umu9F0wRbrRRUpJtXtAG+NX83A0dOYQ1LJgYOXE8MCSUi5ryPkgMKNQkiEOqwLsoRY6 SGUiZDKnFw+x0WjQcnxnmx8ysrgZQ8Ok7bUNkGYiV7RupV4vQ/eoStrOuhyVpCesWvtZ6qNQFPg CQLaqyAJpNa02Qg== 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 --- 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 96611e90ee1e..95d398d99fd6 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', @@ -39,6 +40,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 Jul 8 09:25: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: 27283 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 DB0AEC3332 for ; Wed, 8 Jul 2026 09:25:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 57D8A660B8; Wed, 8 Jul 2026 11:25:29 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="dGW1yM+z"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C94EE6607C for ; Wed, 8 Jul 2026 11:25:10 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2C2AB3FAF; Wed, 8 Jul 2026 11:24:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502661; bh=ZQB2/8/lfwl0b3X1amVOEn3MSG9P/pJyN8UbLOUxTSE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=dGW1yM+z5kSEMTgWwXORnGww72Vlxw35BV9WssEYDFNtD8h1rbH9ymIoO9hG9b4Tl 7UwfVxY8KwUgvIklJ5T8XQwpvjwud5K+pYEIUJjRhp1/0KwftwPH11OQLeVKTyuDjg xBuSZxRIiYrcFpWaJxIeo+9T1o72m70vi1J0Loug= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:05 +0200 Subject: [PATCH v4 18/35] ipa: libipa: lsc_base: Add sampleForCrop documentation MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-18-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1459; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ZQB2/8/lfwl0b3X1amVOEn3MSG9P/pJyN8UbLOUxTSE=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdu1GCg42iOFvniekYWlEumB09cx7jdDVhX2 PpR8BTEzmuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbgAKCRByNAaPFqFW POAaD/9i0MV8LUMT20wKkqlyTUP3GnwukvWNG/dSWQ60ATB6OCau5jaFE8zdf06Eqwa4QAk8j7F 1VnCz0nG8CZlghQE17R759sd+PXbzI2XsQYEzWvZGJ796zz8vSWmEnh9waNXIGtNxWdn55lQUWc hXE7qYr24CuaYiMafqdBVNlm9VBcQAMNnZ8Il7p2Bv6eD8TbdPnU2yudpKlwLThDFmgFPzZ2dB6 JjqnLIFVFYXTgAMVi7YivGBapXq61diW+S0J3UN0qbwyLeqMVO5K3+f9jm3M6obC3IsJ31Oqhmf LC2J12jm4aw07FXcmI16Tf1qRVd97xTh6NgXY5wWrnUWyjHUUiXttKfqfHX/9uu4aEcDUwtnjdP +fthJgpEt9LUXc0o/AsfpSM5xHEZfGH0f5efuw2vdeKJ+BK+uHhiW149FXYuYsVATsmqObnpCnb 7bXIDcDnte4JaALj2PIqsirxR+aS0LR/HCxXxJiyVfef+Io8MwKoS4jGA4Kxf7wgXiKH42dToCn GVYZtQf7rIMEOd2RnoDYxSlOaGE0esjAld1s8HyZNAnpmqKPjnXAMAhJCJoNW8sl4rcthZ2exAV +UPRrkRAl0iMXXxTgcP7Urfe+BQFAfhiDRX00CVaB6hLtUmul7OBbhijPBlTN8CSn/NWnppW9HS gcmlLIzlo2ZGZPA== 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 --- 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 945020794b57..678ba8a9a808 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 Jul 8 09:25: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: 27284 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 51FA2C3333 for ; Wed, 8 Jul 2026 09:25:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4E48366098; Wed, 8 Jul 2026 11:25:30 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ee1BF5ww"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 19C4B66081 for ; Wed, 8 Jul 2026 11:25:11 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6C6163FB2; Wed, 8 Jul 2026 11:24:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502661; bh=pxt5DHoOyWUjji1QJzKfDgjBXNlzEGrF4ewNXFDC9v4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Ee1BF5wwIHsDSYx7DknK/GuOaLXz6WSe4ZadLYtmJTaRavcD96zsE907W6qIyz2bV SveEzplIIOBqQE9cfLvboQ9A8IB1BtANzu4/v+u/bdmiQAo9qf3NxHLmqVp7tQHNYJ c+g2OQZASsPoNMIqvygJK+y5ZMFsFdPnQSwIk3cg= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:06 +0200 Subject: [PATCH v4 19/35] ipa: rksip1: lsc: Make parseSizes() a class function MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-19-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3547; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=pxt5DHoOyWUjji1QJzKfDgjBXNlzEGrF4ewNXFDC9v4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThduUjtdsaA+F11YAnEw1FcaUgCSjoxja8ZEs 5TNvl6w0fiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbgAKCRByNAaPFqFW PIj9D/948/X3yjENqLFvkOTExxhET4pDm8LX2WkFr4aqZJAbsWWHBW7k9vgi9DT/btqt3Kud60l tzbFkBAhb78aq8TwuSgZ/4ycxlK3QGzsdw32JiD4/o2mJeG+oqMVb+AyjC/0VaSHQHm8l/CjcT7 TrsPRXVWQ3wBRxyLCZ2cPBi3tnVxWjbZMNf5Rpznkp6Js3Bf/uz5v2xHZ+2JUEjA1Tk3zMftNnq KBc5TsgpCwlHM1oXOzZGqAEorJzeC8RQGPCcFm9pg96W1Evp8naxIrbq3fyDvbFuKt8g0Fmmei6 xWOwmzJ7QVmHSJ+q+DEDpga378a3NOC8d5b6/ZcR/yDK7trf2OFhG0EDKD4eJRoSBhNPTOuW9Wg aP8Vt2T0Mupc4FcxERCzodZo8/WTgu6kuARBz6e4NzIOOF5pMXaecytq1JxP7EkOH/K9kHkYzOB L2Q2uw/0kJWTCqnLKr0XlZcePqh2idfnZXYTtsm1AEieRv5DZJbtyydPUTX4/ucc18Y7bsgSNnN 4gpdWCmeof7qRGLzdt3h62Ljmp9A3HzUSefHvD+fiPE0bwQVnzT+mLRgTsB9fvt44uUKPa7yBcA ZSNt0IIH9hZyoizuxCLucGjdj7nIYPtbtMFv1wWM8LT6fM6rGyvUuMxy4O4kq+h8/SpfEEmMI0y TSQZfN5ZzLEaKOA== 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 are no reasons to have it as a static helper. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/rkisp1/algorithms/lsc.cpp | 60 +++++++++++++++++++-------------------- src/ipa/rkisp1/algorithms/lsc.h | 2 ++ 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index f32c46084dd1..e03bf361c4b9 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -34,36 +34,6 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -std::vector parseSizes(const ValueNode &tuningData, - const char *prop) -{ - std::vector sizes = - tuningData[prop].get>().value_or(utils::defopt); - if (sizes.size() != RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE) { - LOG(RkISP1Lsc, Error) - << "Invalid '" << prop << "' values: expected " - << RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE - << " elements, got " << sizes.size(); - return {}; - } - - /* - * The sum of all elements must be 0.5 to satisfy hardware constraints. - * Validate it here, allowing a 1% tolerance as rounding errors may - * prevent an exact match (further adjustments will be performed in - * LensShadingCorrection::prepare()). - */ - double sum = std::accumulate(sizes.begin(), sizes.end(), 0.0); - if (sum < 0.495 || sum > 0.505) { - LOG(RkISP1Lsc, Error) - << "Invalid '" << prop << "' values: sum of the elements" - << " should be 0.5, got " << sum; - return {}; - } - - return sizes; -} - unsigned int quantize(unsigned int value, unsigned int step) { return std::lround(value / static_cast(step)) * step; @@ -138,6 +108,36 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, return 0; } +std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningData, + const char *prop) +{ + std::vector sizes = + tuningData[prop].get>().value_or(utils::defopt); + if (sizes.size() != RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE) { + LOG(RkISP1Lsc, Error) + << "Invalid '" << prop << "' values: expected " + << RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE + << " elements, got " << sizes.size(); + return {}; + } + + /* + * The sum of all elements must be 0.5 to satisfy hardware constraints. + * Validate it here, allowing a 1% tolerance as rounding errors may + * prevent an exact match (further adjustments will be performed in + * LensShadingCorrection::prepare()). + */ + double sum = std::accumulate(sizes.begin(), sizes.end(), 0.0); + if (sum < 0.495 || sum > 0.505) { + LOG(RkISP1Lsc, Error) + << "Invalid '" << prop << "' values: sum of the elements" + << " should be 0.5, got " << sum; + return {}; + } + + return sizes; +} + /** * \copydoc libcamera::ipa::Algorithm::configure */ diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 2a428293cb2e..ee037353bbe4 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -39,6 +39,8 @@ public: private: void setParameters(rkisp1_cif_isp_lsc_config &config); void copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set0); + std::vector parseSizes(const ValueNode &tuningData, + const char *prop); std::vector xSize_; std::vector ySize_; From patchwork Wed Jul 8 09:25: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: 27285 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 D6F30C3323 for ; Wed, 8 Jul 2026 09:25:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F118E660DB; Wed, 8 Jul 2026 11:25:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="XiqHybRn"; 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 6A7CA66094 for ; Wed, 8 Jul 2026 11:25:11 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B89CF3FB7; Wed, 8 Jul 2026 11:24:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502661; bh=8OCi4uYq/mK5icgwliwM3R+3rB9MckbE7JtDIY89XRI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=XiqHybRn8W2eop7/kh0oj11xYv9Fz1YQsCK2L3VO/3Ik9XILYQicj2BYEsBkibtlr frSrtWD6WvTQKOcRcO2xF3A1TCAGwbnFH/TB+TE6fFk73tsc0naxHn3J6KOIqYltqp AM2icgRITBe5SeFmj4ZGkRWJ6HzPmUL1TBauI4p8= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:07 +0200 Subject: [PATCH v4 20/35] ipa: libipa: lsc: Introduce lsc::ActiveState MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-20-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=2220; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=8OCi4uYq/mK5icgwliwM3R+3rB9MckbE7JtDIY89XRI=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThduAX91tfKpYmmyhOMpOHx5nippHnHV9iz+L uDKSEc/Q8GJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbgAKCRByNAaPFqFW PAMGEACzLPvgVMvWfkaYXYMG9a7ZcmQMWi7sbLWyFGN73G3CBwQykA49v7uL+MnoXTEWLZZzJVM 6t3Cpii1E4dwwd3k5oJwPMHWuDcd4Pq72yu/s/8eifTq1DqOvE1nvsmrraWZZVYxJdV1CNp9660 60T7o8hq6LkX8T3hzMLANrGYqe3veeoR4g8osG+AGCmpnWnEuGSgGXiB2rSBJSZhAvbE75j4DEX gMD6lPIUeh1L1kPR2AARiOyUcxepTljHINXbT2PJt9+bYnmZiRacJB7cxCt+ZzA/i12KATLdZUd fZm6yMWQnzf4fM6U2G08/FeDQ4pTochvdSEf8ewUrOEn5f/50TgcFoSux8pEeYPBgB1E2o9AvaD TiIXNrrPBJQHcnJ5TaNkaYuL9fEo5LZz0deFZU511VRis9Ki0GHYRRThWoPVLOtK9OmEeGNjuW3 +ZvTpBqmo7QN24S37RjzAIXe2bZEM+DgKQ95pakuhlh8WE3JKdY1HMWsIAA4X/6ZTWhZARqwqMI FnvO7l0nCK8ij1DDsE4kvFmf4Jm42jLNtK3bP3xS4jHw20XklNpCuGPWjcdqEVRuvr/n+rzMSua ZonNs8NiJ6iHA+XU9LCd8hQbeXkqzgYGuV7rdjWCnWIkiVeL5qeATF4cm32X2hdWgQGFBL+vC/j gcnERVEiLeB8R7w== 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. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 33 +++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc.h | 24 ++++++++++++++++++++++++ src/ipa/libipa/meson.build | 2 ++ 3 files changed, 59 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp new file mode 100644 index 000000000000..437666855354 --- /dev/null +++ b/src/ipa/libipa/lsc.cpp @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithms + */ + +#include "lsc.h" + +/** + * \file lsc.h + * \brief libipa LSC algorithm + */ + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +/** + * \struct ActiveState + * \brief The LSC active state + * + * \var ActiveState::enabled + * \brief Boolean flag for the LscAlgorithm enable status + */ + +} /* namespace lsc */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h new file mode 100644 index 000000000000..18c9fa25e53a --- /dev/null +++ b/src/ipa/libipa/lsc.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithm + */ + +#pragma once + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +struct ActiveState { + bool enabled; +}; + +} /* namespace lsc */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 95d398d99fd6..42f89d9820fa 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', @@ -38,6 +39,7 @@ libipa_sources = files([ 'fixedpoint.cpp', 'histogram.cpp', 'interpolator.cpp', + 'lsc.cpp', 'lsc_base.cpp', 'lsc_polynomial.cpp', 'lsc_table.cpp', From patchwork Wed Jul 8 09:25: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: 27288 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 84E7FC3336 for ; Wed, 8 Jul 2026 09:25:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 05B06660C5; Wed, 8 Jul 2026 11:25:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="a8/qhCtQ"; 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 A3ECC66096 for ; Wed, 8 Jul 2026 11:25:11 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1007D3FB9; Wed, 8 Jul 2026 11:24:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502662; bh=OjEaSi7Zm2cxiy3p7wUQy7fR0Qkie3PU1PpBJczMegw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=a8/qhCtQ+r103tcA33YaGWgKLNG2j1VyCT8yGOgV7SM10CTr+gL9iOi2+uxA2Ozj+ SVSoXqGiO/PxBqAyXzwXsU7ctCnMnNRHGrEZiE7hT0JmHn/196yYKbMM+XU2XgWa9h nsxJH3tj+xTcQLchgRZ9fhidnZLP3tM1ZIqtIhYk= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:08 +0200 Subject: [PATCH v4 21/35] ipa: libipa: lsc: Introduce lsc::FrameContext MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-21-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1177; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=OjEaSi7Zm2cxiy3p7wUQy7fR0Qkie3PU1PpBJczMegw=; b=kA0DAAoBcjQGjxahVjwByyZiAGpOF26i2MVUru8n36Yf8/OMjRVgoLyxTrIg66rek0B0Jb5xT 4kCMwQAAQoAHRYhBLXEPUiAmiCKj1g4wHI0Bo8WoVY8BQJqThduAAoJEHI0Bo8WoVY8QoAP/0BV Y8zXIjAawhoAk99aHAO/FjCysPxouwhN6cpvGE+6nfUm2c91zE/kSl31idzjLWPSByO6yWGIBLw UYm+NoW9mP95qdFoIFShSosoM1+LEvDQIq1Cy94PceuFqiyr33WLfs+u9O80gHG6LVhDjNVpRhS 5gPHwlvfjMsJ2JGYcEKlncVBm+8lCYAbFGvfbXaC06/mits1N+vEZUQZCB9Mv6m9GT7yUP1bueD 4QCnGP6iW04vIn94q7pByverTEKBL7kWLJeqtd/cOyBRcuHhHAf81o7/zfXInAumkP6Z4X88a2g ymNc+35pa5CSHWGbqdXh1ZbIk6afV6/uPHkUflX0z4+gQjysxShSAtb8+rcJ3kYYC0TkQDdeN7w XBe04moLBL1pgwC0Nen+8ha+P0cM2e43wcTTXR60qWw8SlGFwT7q2Dv8PuLTPS9dykM/pMCJpeE tnLT+VIkcrBX1uauK0OkfymBfQpQJ/Bb20mbZYg9LfrnVFHjJFLcmZGwXMq9HvxIRZmRh4dQetT LXFDRTG8/aSVe+xEWdJ/bytz2u0nnTjcJr7K5t316kHn6P49dZ+x67f8q56c34wnt7Y4VJPCIfT CfMuMy6mAP3wh+D27zvYwcVlikm7uGwTijdIDgduAUq/SKsqHhWk2Nfcfw/lJtssq7wPfaR0V3E RwoAD 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 algorithm frame context in lsc.h. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 11 +++++++++++ src/ipa/libipa/lsc.h | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 437666855354..382cafc04ab4 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -26,6 +26,17 @@ namespace lsc { * \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 */ } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 18c9fa25e53a..73519564feff 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -17,6 +17,11 @@ struct ActiveState { bool enabled; }; +struct FrameContext { + bool enabled; + bool update; +}; + } /* namespace lsc */ } /* namespace ipa */ From patchwork Wed Jul 8 09:25: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: 27287 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 1A857C3318 for ; Wed, 8 Jul 2026 09:25:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DC082660C7; Wed, 8 Jul 2026 11:25:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="aUfTV8+/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DF6346609A for ; Wed, 8 Jul 2026 11:25:11 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4F6B03FBB; Wed, 8 Jul 2026 11:24:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502662; bh=ufGN47Y+M2apUceF3o/l4FY6OxM8VSz3Mf75D2CYr2A=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=aUfTV8+/AEwblF3JJ65SXTFkTdJpsIdk/+9YKoh6+Q54A6KrTl5BapeLeeaTcpeSO gzfKlOkc/vwtj25amMjnsA7KtfqSugx6ey+UfVO398BmgzDJe+1uxWRBzo2fN8onof zdi1HXhhYtD7i3+Jtwy5vlJKumWcsxCsBADBN2VE= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:09 +0200 Subject: [PATCH v4 22/35] ipa: libipa: lsc: Introduce LscAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-22-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=9923; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ufGN47Y+M2apUceF3o/l4FY6OxM8VSz3Mf75D2CYr2A=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdudRhbcyWqR+U9zdnDdHB8Emni0MHXOgd4K ucHOJzQwHyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbgAKCRByNAaPFqFW PNGnD/9nn3D+4WLLRqMoEC4wMkS3gwXsv9IrH9BkylSYYARwVIBryXMgGs7xpjpW/9TL4lolOra Xi6BSOZXgVhfvDEfmZo7AUFrbnDNUPDWLRuymqbrUzi2Pnca+8ndCj3BO8oLh16Jo3NGOVg4swc Y2jKc2WkfX1Q7gWxbt2sB8HbeOx+/3prz00Z3M5OS2eIUPSoqftry8InhplVDnZiO/mRR721oEB +SvG6CGM7eesnAsETjctjrb08Z2i/H0fR1TnOWMMp3PUVSSkpkmTAzWyfE+oP3LiBDuGV1gmNBx dNWXjJ6eZyI9HXQSuh+jq8lvZuCVyud7bcdrw8VeMeqFeZN0WnvavQflYcsPlW8stI+RZQ/EPgm tThf3SYohQ/sGPs5wp3J6e0v09fHsOgLSvIkBzVhEO8RqcPFC7a+T2NTNP/38mtNVl5uuGcy+gx ptmhfTSQPIoJ7Di1RVXibgFfN82eVMQM1S6KD34MF+xsUY0oSI5JLWnFJU+BBQaFXo9sbuRfIfh iwOr+QPAJKUX58tAjOYilG2WiN4rPy+rK4wKUgBlvYSKXjnAirVWSuNOL6Op2Iv4E25m8gIkgQG 7pLQ08oehxDEhvUh8dc8qMgG9hXUqFq6+1Mr/TWMpp5m0MLv8RV49Ysetu77EAzR0e4MYkQXp4I xL7kmvtpPHkGD6Q== 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 LscAlgorithm class that implements the LSC algorithm for libipa. The class uses two backends (LscPolynomial and LscTable) to perform tuning file parsing and re-scaling of the LSC gains. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 209 +++++++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/lsc.h | 42 ++++++++++ 2 files changed, 251 insertions(+) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 382cafc04ab4..cf748b766d1a 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -7,6 +7,13 @@ #include "lsc.h" +#include + +#include + +#include "lsc_polynomial.h" +#include "lsc_table.h" + /** * \file lsc.h * \brief libipa LSC algorithm @@ -14,6 +21,8 @@ namespace libcamera { +LOG_DEFINE_CATEGORY(Lsc) + namespace ipa { namespace lsc { @@ -39,6 +48,206 @@ namespace lsc { } /* 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. + * + * It provides support for parsing the tuning file content and generates tables + * of per-colour temperature gains that IPA algorithms can use to program their + * 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 organized per-colour temperature with per-colour components gain + * vectors 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 gains per-colour + * component 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 index 73519564feff..9a51d32c3464 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -7,6 +7,17 @@ #pragma once +#include +#include + +#include +#include + +#include "libcamera/internal/value_node.h" + +#include "interpolator.h" +#include "lsc_base.h" + namespace libcamera { namespace ipa { @@ -24,6 +35,37 @@ struct FrameContext { } /* 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 */ From patchwork Wed Jul 8 09:25: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: 27286 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 50CB0C3334 for ; Wed, 8 Jul 2026 09:25:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0FA7B660D1; Wed, 8 Jul 2026 11:25:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fv+fFNJp"; 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 35A5D6608D for ; Wed, 8 Jul 2026 11:25:12 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8B25F19A2; Wed, 8 Jul 2026 11:24:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502662; bh=DRmqcmcgmgwa4JfnoVI42pgp1VvITHEjM0jUXbKB/D4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=fv+fFNJp/NG0nOucSgnPynjWoNoggVvGqUXZcSBhPBfTtzz5QKVjE6HDaIg7R2oTw obF/rFx353plN2Vu+5WbpI7uAFaj6FdSnL/iXBJV14UOkUZPEmRmPD9VVKZKO9jXkR g7cPFRAfq/hxd8IRaAVxXgwqWmGTqj78RIyzX6dc= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:10 +0200 Subject: [PATCH v4 23/35] ipa: libipa: lsc: Move sizesListToPositions() back to rkisp1 MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-23-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=9808; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=DRmqcmcgmgwa4JfnoVI42pgp1VvITHEjM0jUXbKB/D4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThduv/fHwZGeD3Y3ygpLe/3vJ3wrmvyxgSjup 8jEPQO3iDeJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbgAKCRByNAaPFqFW PIuvD/4+k8jz/oHvtDYSFAyg9oTgM7Nk5vlcdwGhp9022dkuI58bdCK/4E/iy5mlNyzhoDeYrso 9W02G0HRsxzYa08iI/hfkIPfkgITm9yi+F0gidJruF1dO5inS7/96A4WZZF1Gv16/wRD8BYTaW1 4iqZh5Y5woHY91N5Z+18HdT8O6O3vSrceX+228Gi0ncBNhMGGzHPy+/ldIZb88Fr2eB/8XY0QKb /9zg5vVy/b8ZYMh8sp9en+U/L5L1M2VP7mKV/tbgfZGNOZhgVuv0xxr841C3hIysWDK2ywp+7U3 cSmQ7ZNBB5nXiyoj3VYsPx/vm4DMmqZhiaaX183uhcXfNnHiz0KEXeHWxj6dTb03YQ5f32n0nRn l3dGE/EP8ELnBXaunFRBrmXuY0d2ZQR6/altXa/jB2lS9KpNmxIvi2/J/CplZdEA87hwbyHFXbn extk1CF2XC1EdLldYl0tbK7MD7Fae4gk+KJC8sGT2+5ru7NkHBuPuA/5hSIqqNyRXULtK4my250 hLnoaIVMTyJYUla7+o5Xg54XCWGefHPS/Vpuy0twtYVajiXTWObXan2OlQKfy8nge8fk4ZdGArn xITu8groE3Apx0QjB20ELQdkyFiYFq8U0H6PielJvnxVT/KBmD6oeWglaXUiBue8SZ51tmEBNWa CIGl7om96HU3Irg== 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 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 --- 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 | 32 +++++++++++++++++++++++++++++++- src/ipa/rkisp1/algorithms/lsc.h | 3 +++ 7 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 678ba8a9a808..cc691e17a694 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. 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 diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 51cc35888209..8d1843a5d4fd 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); }; } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index 6da6dfdea9d4..b5561bbac870 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 e03bf361c4b9..7010c97db22e 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -70,6 +70,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()) { @@ -138,6 +141,33 @@ std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningDat 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 +LensShadingCorrection::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; +} + /** * \copydoc libcamera::ipa::Algorithm::configure */ @@ -171,7 +201,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 ee037353bbe4..06aa7a5f133c 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -41,9 +41,12 @@ private: void copyTable(rkisp1_cif_isp_lsc_config &config, const lsc::Components &set0); std::vector parseSizes(const ValueNode &tuningData, const char *prop); + std::vector sizesListToPositions(Span sizes); 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 Jul 8 09:25: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: 27289 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 2BADDC3338 for ; Wed, 8 Jul 2026 09:25:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D46AC66094; Wed, 8 Jul 2026 11:25:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PX21CSKF"; 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 788E466074 for ; Wed, 8 Jul 2026 11:25:12 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D077CE9C; Wed, 8 Jul 2026 11:24:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502662; bh=AbFShManA6ICgctzKMYWXIvYMk/Czw4c5Qve0xdr//4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=PX21CSKFjfpJ45gRv3wGjgIllUKDJ/7hrDf4qyA/nevthHap7XacNGDmljOdiRGPA IIPEV2oiOn8R+Hsche5iKGEXiKF1BgLBi0bRi9enJs78XH55TDnPtXliD7uvNWGnzh /Tvd4/Ag2Mn9oaJBlwm1Adqe4/hrk2qX+djNHNq0= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:11 +0200 Subject: [PATCH v4 24/35] ipa: rkisp1: lsc: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-24-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=5866; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=AbFShManA6ICgctzKMYWXIvYMk/Czw4c5Qve0xdr//4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdvPJr9jrXXPkdDZZbJcEWDhZ6I3VRgwv812 BCO08eoBK6JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbwAKCRByNAaPFqFW PBLLD/0cZ94FCqUEqDOvHAZiPwAWrSVoBoBoTQjAf3k1nurKke6cyr9sMN3nT0txBTEIfpgfZ4c 15N/qrfb3tcbuj5nyxVvxoPHIqcWdV1JVDIfLKcBiBegOyS4M4j0z5USCAe7irgiCMfr+uBEjY9 XwSTvqv3jRMNbutqVvxGEyK6xqQLTJ0z8Xj0FDbr6/T2fRMXzrDliUhTYG/t6fUvpDieuFysu3+ xPCYS11BynYA0MbkTSOIlTB42cAjBvTzJbGOXCJkhA3BIgZRcysFS9nho7JytTjc2kEeIObtWrY ag49szIdKVQdMDmqYtW+uwJ++m6Y/qi3QsNRNWH5W2RPskBJmMJ90vXx4HxGbmbvK8/8ghPhjC8 +5CtuI4TtbE5i8OYTZEJxCIOeFycPSktz9HSTwGZPzAbaNSC7D7h9JyduKHbCDDGBMPK7QaiAKE 6qJulazQ6JAljAi9yIeE3ooDx/EAR/REe1nI46PHyvC7Ipt8M4XHEwiqfT7snAQEKX+TNSoZ83f XESIv1+lYoWNcO5PP2SdVgH8DJnx0r+tuoSsOxSrxXJHHXysgEqaAKzA5Evm0I66AmUqAg62glO 9/BDr46jCamOpMrskPdGREfjQgCXT7JLu5hzA4WCY35D9WO2JOiXjD24Dn6gSxbcft2Pey03o6I +a/eKeTSNsaUAYQ== 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 --- 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 7010c97db22e..0d9f1b006107 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 */ @@ -73,42 +67,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); } std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningData, @@ -199,13 +159,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) @@ -233,19 +188,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); } /** @@ -283,7 +227,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; @@ -303,7 +247,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 06aa7a5f133c..e7d3091a36c5 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" @@ -55,9 +58,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 Jul 8 09:25: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: 27290 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 E968FC3329 for ; Wed, 8 Jul 2026 09:25:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A603D660C3; Wed, 8 Jul 2026 11:25:39 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="dbDRlFGq"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C57296607D for ; Wed, 8 Jul 2026 11:25:12 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 26A763FBC; Wed, 8 Jul 2026 11:24:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502663; bh=LCcR1MHP4RLSitgNAcBRc+2f76+n6A97ir5UCCJk9dI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=dbDRlFGqrjNnO8r7XqOIfP9eg6FA66ZXcAHGpWH4pGealqjmVInKOV1qD17tv2gqW 5Xp7de2bjdirYyEidQOdbMD+TSAoXmI6NFaooGYMkVQsvfV0oVNQOW2emshWnEB+bC F09qY2DA6EDZcpBWcoQM9oZ91if8924z5laTvwfA= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:12 +0200 Subject: [PATCH v4 25/35] ipa: libipa: lsc: Make Components a map<> MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-25-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=17939; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=LCcR1MHP4RLSitgNAcBRc+2f76+n6A97ir5UCCJk9dI=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdvVnihA9W80Z1Ra6pqRQxR0VuR82yHBGr2g xXglpERpGGJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbwAKCRByNAaPFqFW POyKEACsIz25QXTwtNJ7jD9/aFFs0sBHrSyMANptqGQ+utQcA2dHnEwz5mbPlO9W6z7NjxkDDIF txokoHY304zCiKgQ6Ljr0lfNrDcuajOQ8PKpzTDOsQwCk9I4PnMqW5LTW5z1Rqw89GAjXRdXnbE H/5k62I47y+bAob5jXz0UI4QxdlPYoo4my/GP93f0IgpIkz22FzGovET2VkC7VkhCJr/tWfIo7i 3a9H2HgjMHwgBIaMs5xHYJaCvVv5A/XXSJsQP36QsbmBx7o4YsTQr0stRl/Ra0Bzu9BIBOc+tUd R23mTUn3yGk2dFEeVJCuIYZqRELCL9P1Pgy/8Vv5xlAeyFqZdvhL065Agz5mYOk6tcse8swiLaP Tz4R5QWKFXfEbMVsLEzEwrLHDusxUUAqjRSTSYv0zjJBwHQoQaAyUeoISdF7McJpBvm+QNFrXry 2BYfZdGLpiRe8zi3s0EEwPOgMNO9jPvdUa7xfogsXmZ9rLIhT3rmEqscR27OWdWIy/HJ5hCOGmr RcYbFbRQox/K971vityI2+HzigiYsE0EtbUT3w2Jfj9RQESg8FyEeuEqviXAK9DvGtQI8TbOtJt LJoI6oM9YrQZkgpms7ik1mdulJ1Ggc/0XlPOkWCDtmDcx+yM6xjsGlRC0GdkxPQ4Z/mTpyrlu+V 7b+NvysIEVkJUOQ== 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 --- 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 | 65 +++++++++++++++++++------------------ 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, 150 insertions(+), 103 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index cf748b766d1a..796c75f6d729 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 cc691e17a694..919622c6b951 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 a 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 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. * - * \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::numHCells + * \brief Number of horizontal cells of the ISP LSC grid. Used for validating + * the list of gains parsed from tuning file + * + * \var LscDescriptor::numVCells + * \brief Number of vertical cells 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 8d1843a5d4fd..48307455362a 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>; using ComponentsMap = std::map; } /* namespace lsc */ @@ -56,12 +50,20 @@ void Interpolator:: double lambda); #endif /* __DOXYGEN__ */ +struct LscDescriptor { + std::vector keys; + unsigned int numHCells; + unsigned int numVCells; + 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 b5561bbac870..83594bb52696 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -133,44 +133,43 @@ 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.c_str()].get(); + if (!polynomial) { + LOG(LscPolynomial, Error) + << "Missing polynomial for component " + << k; + return -EINVAL; + } + + auto [it, inserted] = + components.emplace(std::piecewise_construct, + std::forward_as_tuple(k.c_str()), + std::forward_as_tuple(*polynomial)); + + 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_.emplace(ct, 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 +209,17 @@ 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; + + for (const auto &[k, p] : c) { + comp.emplace(std::piecewise_construct, + std::forward_as_tuple(k), + std::forward_as_tuple(samplePolynomial(p, xPos, yPos, + cropRectangle))); + } + + components[t] = comp; } return components; diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index d2276ecc6f6b..2a26db499d68 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..cdd2fea5ebf3 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.emplace( + std::piecewise_construct, + std::forward_as_tuple(k.c_str()), + std::forward_as_tuple(parseTable(yamlSet, + k.c_str(), + descriptor.numHCells, + descriptor.numVCells))); + if (!inserted || 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_.emplace(ct, 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 numHCells, + unsigned int numVCells) { - static constexpr unsigned int kLscNumSamples = - RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX; + unsigned int lscNumSamples = numHCells * numVCells; 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..3fe52afbad7b 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 numHCells, + unsigned int numVCells); lsc::ComponentsMap lscData_; }; diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 0d9f1b006107..9db67197ad64 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -67,8 +67,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" }, + .numHCells = RKISP1_CIF_ISP_LSC_SAMPLES_MAX, + .numVCells = RKISP1_CIF_ISP_LSC_SAMPLES_MAX, + .sensorSize = context.sensorInfo.activeAreaSize + }); } std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningData, @@ -174,10 +178,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 Jul 8 09:25: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: 27291 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 92DD5C3339 for ; Wed, 8 Jul 2026 09:25:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 43CA8660E0; Wed, 8 Jul 2026 11:25:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="IJzlHq+w"; 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 24DCA660A7 for ; Wed, 8 Jul 2026 11:25:13 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 703643FBE; Wed, 8 Jul 2026 11:24:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502663; bh=rQpuQHXCyAXIKUP2x9f0JycAyoGrS4OUcp9j0GEys7U=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=IJzlHq+wEPs8Ah3e4m48azngswhifYreXNUMDqYTxzsyT6515LISibP1w2RnWaDYy xzJ3ZB46yvtW0uqAXVWgQQyy85cBkSDWUc/WnGUEuuuNKfcoLpkKnb5yHBg0V0L6B4 e28STmh27Qkg0m2oIl1Qs05fwe9ya7BELj5YhXks= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:13 +0200 Subject: [PATCH v4 26/35] ipa: libipa: lsc: Document Components parsing MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-26-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=4242; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=rQpuQHXCyAXIKUP2x9f0JycAyoGrS4OUcp9j0GEys7U=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdvUx2HTn43uPFM72pCYALjQRxGvrMNKf2+X 6bpsm35IJKJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbwAKCRByNAaPFqFW POs/D/4ptHnIOv5v9D2XusHrbvj03vMPRETg7vzT0zqWTPa8CHGFM6RboCruo2w/VK/Iy+lRyZu pp8mMq+Gs6U6mEZukVgKoAFfD270gm99O+NMGLZhiGBl08/AriHcP3POlKQ2Df9/L9cn8CaFmzn W76HbnkldrnHHG60YPnahbITCFopf8BnGpv/QQNz8PQp7/hZ2PVIwAgSb9tDNbC/i96mQV2qOsf 9WWzZYozMIiPgedS1VfS4l6M3rLosBoHUmpSLAOrrfXM/HUpLVKTAilKMF0IK5X3tWHqS3dlKYx SxBHixHzkTK4k6HxkIUFndTeo+UNbTuzM7MjglN7PmUNJcXGVTmYzY1dl4StZbd+N/oUEmCbVFn fuhxXm2mLzE2K7yhl/R3E8gy1mGxvR+oi5WCk2BtGs9goHqKc5X+mQk9v+s1JZIjSOnKM7ls+fj oRUqb5XL+vtnmJF1qNA5Zr0TtQ51yf0nuy0k3E5s25qHdiWi4CUhoZZjOJ5kTktUMHTkolBLsxW wnys3qs2NGAbgtqAOukbAd0opq5pqwfwbbm7JRtdV1aMGPP8KVmcVsACwSCyGi//ColeSX729qF Wz/HZ3fqsZEMTuC6UaF7C+MUzYYxQgsih5/iRQUYYB6+VxWOd6+UWuQSgyA7ViUTL+fU47CmVC1 2F7AwZj9WsafJXQ== 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 --- 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 796c75f6d729..26c3e9968072 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -68,6 +68,117 @@ namespace lsc { * tables are organized per-colour temperature with per-colour components gain * vectors 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 here omitted, but the expected number + * of entries has to be equal to + * LscDescriptor::numHCells * LscDescriptor::numVCells. + * + * \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 Jul 8 09:25: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: 27292 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 3AAE4C333A for ; Wed, 8 Jul 2026 09:25:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C9AAB66098; Wed, 8 Jul 2026 11:25:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="JxUAW8A+"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5F3D96609B for ; Wed, 8 Jul 2026 11:25:13 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B79C31CFD; Wed, 8 Jul 2026 11:24:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502663; bh=MRTK0aOxYFXDosRsnpj0oYQp6lv1gMPMd910d2p1od0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=JxUAW8A+zKxlDZoNsxoBfjhYiyGi7D3Zrfb300hCgxChiihukwVpLq1NPJIa9rsXe gWE7GfBetDLslihohoWQFEpsZIrxrzBAS4MumGHI5Ot7y+X3pFSIlsMRKU9nlH6dDW goMlgReBOktl1q7aErgFIaZQb6By+Ta++MbP8DsQ= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:14 +0200 Subject: [PATCH v4 27/35] ipa: libipa: lsc_polynomial: Remove sensorSize_ MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-27-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3254; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=MRTK0aOxYFXDosRsnpj0oYQp6lv1gMPMd910d2p1od0=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdvmfQw/9/RzQZJL5NQHcdi1jaKu1mlk2y28 LJlfjU9rW2JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbwAKCRByNAaPFqFW PFCND/sESeWf1aZTT9lpH/rrhOXpitjPXagGW//CFN4B/BwZr/OdKhZ4Evc3edb5XMln/PJ8S40 6r11wApQO7tPrGx1IAPnYoox0i4rUp/ZpjCBaBWqnzfXhKfkEGoyw68lt8OZlXcAgWnAQstaAzR jwTse3jocELUZ+3SsWLbFzNx7t8mWpUB9Y13kfHdVhY7bO4K4/IMJT1xqJjqScRv+tJgIBBBaR3 V3s8QpMmhwDMmdDz9LsJVh8PldOGG22yxFf0rc6ke5qvZQ6fNJomxFbrU6WRFvLukvlkd7oXcfA tgkeQJoM673JOIHwGMPmEO1iB5VcYB8hNDZcqpafMq22LXJExt/S0K2PGT3tV3KjbGw4/DK2RFT LNP6sfK8On5b66GQSzS183ZuJfpVZqwpzKbIZS9URQZ4cycL6a9KIlQQ1yy40RF+82Fbc6r90im uXQHk43Eqcs7vBt9GI3O3Hb1X9niZKWuq2Pibhd6NAkr70AjYuGyv0Xh1n/7uiy0deNiXRB++uL YjlPnTdzL5bP7jN0NeQzLURV8pimJse9wCUrdNj6vJ9V14bg3lHl1jPhCVpXNq+S3HOC2uQCEvW 1rcsqDrvA+evdwSun8q7Pz/dS0a5DT0ESufvLky2nb0GqVBhu7zi+o2I/k4S3ToY/uSm8SYbxRK 1pn0lEpLPt9rB6Q== 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 --- 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 26c3e9968072..1d2f8eb1b41b 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 919622c6b951..adc09c15752a 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 83594bb52696..110b43157ff3 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 2a26db499d68..6d19e46a50b7 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 Jul 8 09:25:15 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27294 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 7B4F8C333B for ; Wed, 8 Jul 2026 09:25:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1E8E5660E8; Wed, 8 Jul 2026 11:25:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fCdZA9/T"; 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 ADA4B660AB for ; Wed, 8 Jul 2026 11:25:13 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 078053FC2; Wed, 8 Jul 2026 11:24:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502664; bh=l3IWqtX/3k/0xTkpngmJmTTxrkKv+Uf3ZsubdNM+OxY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=fCdZA9/T18Pauuiuw+bDp6QlL4C/XvTmgbCOwHGhJeD9w8ArPdVFXj9Mnyyo/y7yf 2hpnSrqtV1nbMPr56BZGWDUw9SoKVueHKE52VL7oVVx57ZZ2KRzqoN8CP16O/w6Epu g/k8OX/VUr6empVouYS8SDWrekOtmYF1INmSM4MM= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:15 +0200 Subject: [PATCH v4 28/35] ipa: libipa: lsc: Document LscDescriptor::sensorSize usage MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-28-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=966; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=l3IWqtX/3k/0xTkpngmJmTTxrkKv+Uf3ZsubdNM+OxY=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdvOi/6kYD2C4jdWCco9TXczXBCO4FDDcXWt ZvqZHgfLNeJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbwAKCRByNAaPFqFW PJtBD/4gk+FRscxrMjYfNJY57wLjyLZ8uU82SBsRtiz8kt7llbf891TKWT0nMoG15H6Z6EJjl31 1ttVzQJCTGu1lFxBHoYVF76oCphQrrd+N0Yr72QA8yhN4ER9AyKUq5Jf/lTZhKgeo2qitQZAYiA l5RlQmZ7Hq95W0YuDU3iJWeDNSelqgyD7cDj/Ihvfrzy/8cofKhRJxNvTkmkWDoj775hW/oiK// DbD0N7yKOJBxa6wkeMjeTzh5DHX5+kTaIi6HbN4R7ZTXTqkeZGX9BwtJ0gm7mF8I3D2ja+EM1pl voGnmDAeiN/6cicINQM7uX7WEmpGQQmjWqvUlU9aFp5IXyDErfX9HJMiIYWJ9XXFv4PR9gc08nu NdLTqs4/JuUNxOvlym9lgkx1+NFmnvdFx3mPNGtMqzdHlGi2363z47tzvRJYNi0RYA1+VBeUI7M 6LiC9sa/MHodzktJNa2drL4AV0j/ImitahDpKJ0uryS3lrQFRwDQrnzjMQ5nmVdXhBxm8UeP69c DqtNnsRJN/wXu00eMn9DpRT4kwk+LzQZ8pBDGInKV74uJi5qv0PWY16tGDUTqcYBQdGS7svUxL1 r+l/R7J+5g/2mUGaUMuMiwK0L9vjOyWCelmVnzcR24LfmX+fKYYkprOKRadiboSjNwJNbU02RoV dk0KzgkoY/Yp1ww== 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 --- 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 1d2f8eb1b41b..13b1b738106b 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 tables or the polynomial definition are generated at tuning time + * using an image of known resolution which 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 Jul 8 09:25:16 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27293 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 D5413C331D for ; Wed, 8 Jul 2026 09:25:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8A5A4660D6; Wed, 8 Jul 2026 11:25:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WXdtYjSQ"; 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 F421266082 for ; Wed, 8 Jul 2026 11:25:13 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 563DD3FC5; Wed, 8 Jul 2026 11:24:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502664; bh=uMZUP/UMCDG22kn4l5L6ybJQxTN66yIwak24piNDrZ4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=WXdtYjSQeE4eRnezrvv1x4klq32d7maMyX6c0H1MXM+tcBabGu+k3Zyx4yeK5qhtD XfsHYX23AYFYuQNhHnBr5bdTvOcwE8hhcIheCF1uLYwoYrhBIFscCo98iAtoc/fxrO H1lV4GfB9VSfEx3dVrXr1LFkGmx6iu8xev6oi6GY= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:16 +0200 Subject: [PATCH v4 29/35] ipa: libipa: lsc: Template LscAlgorithm with register format MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-29-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=23339; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=uMZUP/UMCDG22kn4l5L6ybJQxTN66yIwak24piNDrZ4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdvHEEVAB/IixpWQC2se3vEsWGbP04cVkzMA 1PwgrOGBxWJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbwAKCRByNAaPFqFW PC3pD/9QtU+pCUdQsU1fcIiRkePXPXlxDkwlv0H+P+nO3vCeWxts31Dg0NO9FM3tipn48x313Cd aKcywSApIDejwM+c16yi0RBNVea80OeFh1Y9LJYznuP3Q0mlIfosRbEUZx0ZV2zYfJvuYIVg322 qn05iGFJBcyWKhzHbzy6H3kYESTC3sAOn7Dle2NkztSLy30+OQeqtmP2EqoYuSHepGo9cFU0nYp /cOg51vN+XflBRq9dDhmGnTsSv6tckHfweV5ak6nID+7G/WbVoC4xG9IYBhSZPNwRdYvduohMpE TGXqT6MS2FizaYs1uBWbUQIO6kTKRKK8wtHTkaJ6j2R3vXf/m9JMPZKJrT1/I3PJDSTVkirjUpT vovuQzzjeoqInpzQMc19D5+M2Vse8PQS55JhTSPF9XWoCXPSsU8VvMl7NCAYjMv0sUKW2qYjV2A +K3i/kfXxEleXvltrPuYyrVOG5u2yzhUCT4Zsina1SKj5EfwJNr12lxW6uSP7xkcB3edSPboX9j 6H2X45Dm/f6hpn74Sk98eVQ0hEtcnXAAbDocIhdjlHxDFXmGDE3NH4gFLOTmiyVlvcaPHAOjt08 u4I95YEqSs84BK6kWrzz7LCg/ZfGivRym7h6hPrB2l6+/dQmPQWd4Z7MldBTrVdJhTJq1DfdTKm Zbo6g+Dnmjlyekg== 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::samplePolynomial function expands the radial polynomial assuming the RkISP1 Q<2, 10> register format: /* * 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); In order to remove all assumptions about the platform register format template the LscAlgorithm class with the fixed point format of the ISP Lsc engine registers. float sample = static_cast (poly.sampleAtNormalizedPixelPos(xp, yp)); samples.push_back(U(sample).quantized()); In order to template the LscPolynomial class, template the whole class hierarchy including the LscTable class that doesn't need (yet) the template argument but will use it once re-sampling LscTable will be implemented. Split all involved classes (LscAlgorithm, LscTable and LscPolynomial) in two classes: a Lsc*Base class for non-templated function implementations and a Lsc* class for templated functions implementations. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 151 +++++++++++++------------------------- src/ipa/libipa/lsc.h | 81 +++++++++++++++++--- src/ipa/libipa/lsc_base.cpp | 1 + src/ipa/libipa/lsc_base.h | 1 + src/ipa/libipa/lsc_polynomial.cpp | 90 +++++++---------------- src/ipa/libipa/lsc_polynomial.h | 67 +++++++++++++++-- src/ipa/libipa/lsc_table.cpp | 49 +++++++++---- src/ipa/libipa/lsc_table.h | 35 ++++++--- src/ipa/rkisp1/algorithms/lsc.h | 3 +- 9 files changed, 270 insertions(+), 208 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index 13b1b738106b..f5646937c60a 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -7,13 +7,6 @@ #include "lsc.h" -#include - -#include - -#include "lsc_polynomial.h" -#include "lsc_table.h" - /** * \file lsc.h * \brief libipa LSC algorithm @@ -48,9 +41,56 @@ namespace lsc { } /* namespace lsc */ +/** + * \class LscAlgorithmBase + * \brief Base class for LscAlgorithm + * + * Base class for LscAlgorithm for non-templated functions implementation + */ + +/** + * \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); +} + /** * \class LscAlgorithm * \brief libIPA LSC algorithm implementation + * \tparam U The fixedpoint LSC engine register format * * Due to the optical characteristics of the lens, the light intensity received * by the sensor is not uniform. The Lens Shading Correction algorithm applies @@ -200,6 +240,7 @@ namespace lsc { */ /** + * \fn LscAlgorithm::init() * \param[in] tuningData The tuning data * \param[in] controls The IPA list of supported controls * \param[in] descriptor The LSC engine descriptor @@ -209,42 +250,9 @@ namespace lsc { * * \return 0 on success, a negative error code otherwise */ -int LscAlgorithm::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() * \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 @@ -265,67 +273,6 @@ int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &control * * \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 diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 9fe8ad67ea33..d48336b169f4 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -8,18 +8,25 @@ #pragma once #include +#include #include +#include + +#include #include #include #include "libcamera/internal/value_node.h" #include "interpolator.h" -#include "lsc_base.h" +#include "lsc_polynomial.h" +#include "lsc_table.h" namespace libcamera { +LOG_DECLARE_CATEGORY(Lsc) + namespace ipa { namespace lsc { @@ -35,19 +42,75 @@ struct FrameContext { } /* namespace lsc */ -class LscAlgorithm +class LscAlgorithmBase +{ +public: + void queueRequest(lsc::ActiveState &state, lsc::FrameContext &context, + const ControlList &controls); + void process(lsc::FrameContext &context, ControlList &metadata); +}; + +template +class LscAlgorithm : public LscAlgorithmBase { public: int init(const ValueNode &tuningData, ControlInfoMap::Map &controls, - const LscDescriptor &descriptor); + 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; + } 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 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; + } const lsc::Components interpolateComponents(unsigned int ct) { @@ -60,7 +123,7 @@ public: } private: - std::unique_ptr impl_; + std::unique_ptr> impl_; Interpolator sets_; lsc::ComponentsMap lscData_; bool polynomial_; diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index adc09c15752a..b91da4e5be9d 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -86,6 +86,7 @@ void Interpolator:: /** * \class LscImplementation * \brief Pure virtual base class for LSC algorithm implementations + * \tparam U The fixedpoint LSC engine register format * * Defines the interface for the LSC algorithm implementation. */ diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 48307455362a..721fd137ef21 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -57,6 +57,7 @@ struct LscDescriptor { Size sensorSize; }; +template class LscImplementation { public: diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index 110b43157ff3..7d4381973646 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -113,14 +113,10 @@ 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. + * \class LscPolynomialBase + * \brief Base class for LscPolynomial * - * \sa LscImplementation + * Base class for LscPolynomial for non-templated functions. */ /** @@ -132,8 +128,8 @@ void Polynomial::setReferenceImageSize(const Size &size) * * \return 0 on success or a negative error number otherwise */ -int LscPolynomial::parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor) +int LscPolynomialBase::parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) { for (const auto &set : sets.asList()) { uint32_t ct = set["ct"].get(0); @@ -174,6 +170,27 @@ int LscPolynomial::parseLscData(const ValueNode &sets, } /** + * \var LscPolynomialBase::lscData_ + * \brief The polynomial LSC data + * + * Maps colour temperatures to per-colour radial polynomial definitions. + */ + +/** + * \class LscPolynomial + * \brief Radial Polynomial LSC algorithm implementation + * \tparam U The fixedpoint format used to convert gain values generated by + * polynomial expansion to the register format + * + * Polynomial-based LSC algorithm implementation. The LscPolynomial class + * implements LSC support using Polynomial to represent the shading artifacts + * map. + * + * \sa LscImplementation + */ + +/** + * \fn LscPolynomial::sampleForCrop() * \brief Re-sample the LSC components for \a cropRectangle * \param[in] cropRectangle The sensor analogue crop rectangle * \param[in] xPos List of horizontal positions of the LSC grid nodes @@ -195,62 +212,7 @@ 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 -LscPolynomial::sampleForCrop(const Rectangle &cropRectangle, - std::vector xPos, std::vector yPos) -{ - - lsc::ComponentsMap components; - - for (const auto &[t, c] : lscData_) { - lsc::Components comp; - - for (const auto &[k, p] : c) { - comp.emplace(std::piecewise_construct, - std::forward_as_tuple(k), - std::forward_as_tuple(samplePolynomial(p, xPos, yPos, - cropRectangle))); - } - components[t] = comp; - } - - 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; -} } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 6d19e46a50b7..04d7465b3af0 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -50,26 +51,80 @@ private: } /* namespace lsc */ -class LscPolynomial : public LscImplementation +class LscPolynomialBase { private: using PolynomialComponents = std::map; using PolynomialComponentsMap = std::map; +protected: + int parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor); + + PolynomialComponentsMap lscData_; +}; + +template +class LscPolynomial : public LscPolynomialBase, public LscImplementation +{ public: - int parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor) override; + int parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor) override + { + return LscPolynomialBase::parseLscData(yamlSets, descriptor); + } lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, - std::vector xPos, std::vector yPos) override; + std::vector xPos, std::vector yPos) override + { + lsc::ComponentsMap components; + + for (const auto &[t, c] : lscData_) { + lsc::Components comp; + + for (const auto &[k, p] : c) { + comp.emplace(std::piecewise_construct, + std::forward_as_tuple(k), + std::forward_as_tuple(samplePolynomial(p, xPos, yPos, + cropRectangle))); + } + + components[t] = comp; + } + + return components; + } private: std::vector samplePolynomial(const lsc::Polynomial &poly, Span xPositions, Span yPositions, - const Rectangle &cropRectangle); - PolynomialComponentsMap lscData_; + 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; + + float sample = static_cast + (poly.sampleAtNormalizedPixelPos(xp, yp)); + + samples.push_back(U(sample).quantized()); + } + } + + return samples; + } }; } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp index cdd2fea5ebf3..0e8f144cb873 100644 --- a/src/ipa/libipa/lsc_table.cpp +++ b/src/ipa/libipa/lsc_table.cpp @@ -14,13 +14,10 @@ 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. + * \class LscTableBase + * \brief Base class for LscTable * - * \sa LscImplementation + * Base class for LscTable for non-templated functions. */ /** @@ -32,8 +29,8 @@ namespace ipa { * * \return 0 on success or a negative error number otherwise */ -int LscTable::parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor) +int LscTableBase::parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor) { for (const auto &set : sets.asList()) { uint32_t ct = set["ct"].get(0); @@ -51,8 +48,8 @@ int LscTable::parseLscData(const ValueNode &sets, return 0; } -int LscTable::parseLscComponent(const ValueNode &yamlSet, - unsigned int ct, const LscDescriptor &descriptor) +int LscTableBase::parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor) { lsc::Components component; for (auto &k : descriptor.keys) { @@ -82,10 +79,10 @@ int LscTable::parseLscComponent(const ValueNode &yamlSet, return 0; } -std::vector LscTable::parseTable(const ValueNode &tuningData, - const char *prop, - unsigned int numHCells, - unsigned int numVCells) +std::vector LscTableBase::parseTable(const ValueNode &tuningData, + const char *prop, + unsigned int numHCells, + unsigned int numVCells) { unsigned int lscNumSamples = numHCells * numVCells; @@ -102,6 +99,30 @@ std::vector LscTable::parseTable(const ValueNode &tuningData, return table; } +/** + * \var LscTableBase::lscData_ + * \brief The tabular LSC data + * + * Maps colour temperatures to per-colour channel vector of gains + */ + +/** + * \class LscTable + * \brief Table based LSC algorithm implementation + * \tparam U The fixedpoint LSC engine register format + * + * Table based LSC algorithm implementation. The LscTable class implements LSC + * support using tabular LSC data. + * + * \sa LscImplementation + */ + +/** + * \fn LscTable::parseLscData() + * + * \copydoc LscTableBase::parseLscData() + */ + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index 3fe52afbad7b..65e04b09c3ff 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -23,11 +23,32 @@ LOG_DECLARE_CATEGORY(LscTable) namespace ipa { -class LscTable : public LscImplementation +class LscTableBase +{ +protected: + int parseLscData(const ValueNode &sets, + const LscDescriptor &descriptor); + +private: + int parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor); + std::vector parseTable(const ValueNode &tuningData, + const char *prop, + unsigned int numHCells, + unsigned int numVCells); +protected: + lsc::ComponentsMap lscData_; +}; + +template +class LscTable : public LscTableBase, public LscImplementation { public: int parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor) override; + const LscDescriptor &descriptor) override + { + return LscTableBase::parseLscData(sets, descriptor); + } lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, @@ -38,16 +59,6 @@ public: << "Tabular LSC data doesn't support resampling"; return lscData_; } - -private: - int parseLscComponent(const ValueNode &yamlSet, - unsigned int ct, const LscDescriptor &descriptor); - std::vector parseTable(const ValueNode &tuningData, - const char *prop, - unsigned int numHCells, - unsigned int numVCells); - - lsc::ComponentsMap lscData_; }; } /* namespace ipa */ diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index e7d3091a36c5..31156f56cd21 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -13,6 +13,7 @@ #include "libcamera/internal/value_node.h" +#include "libipa/fixedpoint.h" #include "libipa/lsc.h" #include "algorithm.h" @@ -58,7 +59,7 @@ private: unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; - LscAlgorithm lscAlgo_; + LscAlgorithm> lscAlgo_; }; } /* namespace ipa::rkisp1::algorithms */ From patchwork Wed Jul 8 09:25:17 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27295 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 2D3D5C3330 for ; Wed, 8 Jul 2026 09:25:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B6D12660E9; Wed, 8 Jul 2026 11:25:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="D4FCh8DG"; 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 3F57566078 for ; Wed, 8 Jul 2026 11:25:14 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9DA503FC6; Wed, 8 Jul 2026 11:24:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502664; bh=2QvgZUzLDc2ja7R9EkinL3mS8x7xRAxO+EYGjrc7vbU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=D4FCh8DGdgA/1PwjmaokNcoJ3JzWo+nwgtqN9ABxikiG431cqcYDGUZ5tgb5U6sUP c+anVrNvJgsGJwfYu5OcLV0cWXxm/GA1S5UkIdRO3BF8jBK93Q5GKbWHCnqnsGJwkW Pp124SF42EpY7C/SOhwpzguAjNsCWRbZLQD5rxT0= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:17 +0200 Subject: [PATCH v4 30/35] ipa: libipa: lsc_base: Describe the function of lsc_base MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-30-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1646; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=2QvgZUzLDc2ja7R9EkinL3mS8x7xRAxO+EYGjrc7vbU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdvi3HVtADvi928wNehQ5TLvHMs5ugSBrCYu Nxq3s2WSYWJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbwAKCRByNAaPFqFW PGGgD/9AXFtm+AyTXz8Bq9ay8WaNaDBLHe2n2vh3IKJQBkBL+0Qgieh4XDm6bOFjsELXigTadtM bnw7Yas1qibuCBnpgqOD0ajEXU+BAo20U4iX5sifeYEH5qB2lTaaGgQU/Z4FOgGMvTrufCw87bR 2LPrEIvKXh635GUVi7n9Dmr/2T69oyHthxHgqpGP1xKBVPB+Bg+K5Rk7w2nRWDrmzX9pwPvDn0P l4IdM55QBFFX/DaniPSgqd2GBgxvm9F3/fxg+bLq3/a8XU7A1VPyeE+sPMgZ0D4z9cq+iQi6A6r XfOq/XkBg7OykkoJnOFIFZoiRZu6RWBJKXntgFZzaqdwzbF3KDZmmXG7nl5fkrGLDdYq1cxdAqp 4sWouyg/71VW3DjkgkO4x/0V8F1mg0gibuDJ0aKk0NVQaXlARFZ917zDFvw+4Y2CSHD8ra15isY Ka3wbua4L2DzN/yOBU8rQuWOXub/5HnZeTerCpuHO2LHg86/qEHPl0D9PDfmM67gXwTcYJhmoih ENx2P7ArkulnT0tjwPXpUQult7oqHtArBLwX9s4Xx7g6m+OjTOu0PdphzJLHqu2XBo3t/InJkx+ E8F+lkppzft79JaQ/yirzXeZmzAVMhO3wihOdqTC6q9cXvWvNRQjLWhaCC7ePGkTtVaJWdpupZy Zp8IBsek+su6kNg== 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 the class hierarchy is in place, describe the function of the lsc_base header in the file comment. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc_base.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index b91da4e5be9d..dd2e1128a546 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -10,6 +10,19 @@ /** * \file lsc_base.h * \brief Base types and definitions for LscImplementation class hierarchy + * + * Split the common classes and types definitions to a dedicated file to avoid + * circular inclusions. + * + * Both LscTable and LscPolynomial inherit from LscImplementation and they are + * instantiated from lsc.h as the LscAlgorithm::init() function is there inlined. + * + * For this reason lsc.h needs to include lsc_table.h and lsc_polynomial.h which + * need the LscImplementation class definition themselves. + * + * Split the LscImplementation interface definition to this file so that it can + * be included by both lsc_table.h and lsc_polynomial.h, which are then included + * by lsc.h. */ namespace libcamera { @@ -88,7 +101,8 @@ void Interpolator:: * \brief Pure virtual base class for LSC algorithm implementations * \tparam U The fixedpoint LSC engine register format * - * Defines the interface for the LSC algorithm implementation. + * Defines the interface for the LSC algorithm implementation. Currently + * implemented by LscTable and LscPolynomial. */ /** From patchwork Wed Jul 8 09:25:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27296 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 A4D03C333C for ; Wed, 8 Jul 2026 09:25:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4904D660EA; Wed, 8 Jul 2026 11:25:43 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DxduKbeX"; 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 97A0C66075 for ; Wed, 8 Jul 2026 11:25:14 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E02F21D03; Wed, 8 Jul 2026 11:24:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502665; bh=GY5ogt0BidgoFAt5zKvDZ5pfXZ8jQRBTkReJfnAGbZ0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=DxduKbeXf7BE5PiGD58EcTXiUpFVJpOzRClGV7fv2PKtj6Ub0uckrzdjALS80k0M3 4Qhqy2m+v2Oeffs4bCpXQ6lXTTWemfw9kp4+ZoS93mxnc60hfoqKKxSBT/Pzmbx99+ X2NZ+BvIaU62/39WNyeoqv7bfV6tnb0fjq/VGvnk= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:18 +0200 Subject: [PATCH v4 31/35] ipa: libipa: lsc: Template lsc::Components MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-31-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=16122; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=GY5ogt0BidgoFAt5zKvDZ5pfXZ8jQRBTkReJfnAGbZ0=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdvKLRvh+Vo42P4H09QuMTag+9jilno1GFWO jWOUqIqw4KJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XbwAKCRByNAaPFqFW PP7XD/0ZF5F6XK5u9Tpfjis19OOeqvcNPXVmEpZwE+oM8tLPpokdPMZzWVmUnXCIl8aWFyy+1B1 lO8iq+kz4C8sYqHxa0FJgMNXZQ8WNVydo24DDXluo3l7YuB/+6fjwkwFDnwZR64SlZXb6NCWVaX h9ucJxMU/PpW1zq1voFBsb7y6uf32vxPpaGOFfeK9+dNuI0frldWHFJa/ofAUFvFjOXUSDvEaQc mygI/aL3Eh2A3QcTNzKT3e17sXNhsizz0TTKjzarxq9BAh1mguq5G/StkBL+JV3kbaFxXuoSD5W p2CWYX/Vp+wv+/zF9vai0MUaaUe5GvPWGusM0Ekz35p/pGUxPlI5YHqKu4JKvf1MGaDdAWhk7rB nbi585JyYRMUG12kOnM3wudncpA0LCQW3rD2/xaFHe5tMYus7TZAOUmCts/GVd4/I+nJRHANqS0 mQzxAjGj9pXytTEO9sV60JBwX3EbTBj7v+irc1VMNsQVZheQ+1jPoIUlu00LDQsPLZsKYArOaOd oDSXKQ9nicf73HqTYu0wIyAmmsfA26Q3lpTTEb6g9meIBk8t+DHIH1KtyTZXzSNlTd8tcIgqidd RpeqN2pROT2GgcV1PYCbrONaSs580bSiE12m8i918Lu2eQCAJMh+NkfxtOOcdXjuSSHaOVAt0vn C5LQ5pVxnRldSCg== 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 lsc::Component type maps a string that identifies a colour channel to a vector of gains expressed in register format. The current implementation assumes gains in u16 format which matches the RkISP1 register format. As lsc::Components is used both to store gains as loaded from tuning file and the result of the radial polynomial expansion, different ISP use different bit widths to represent the gain values. Template the lsc::Components and lsc::ComponentsMap with the type used to store the gain values. Use the Quantized::QuantizedType typedef which expresses the integer type required to store all bits of the fixed point number used to instantiate LscAlgorithm<> by the IPA module to template Components<>. The LscTable class now requires to inline all tuning file parsing helpers, so we can drop the just introduced LscTableBase. Specialize Interpolator> for uint16_t as this is the format used by RkISP1. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.h | 13 +++-- src/ipa/libipa/lsc_base.cpp | 19 +++++--- src/ipa/libipa/lsc_base.h | 21 +++++--- src/ipa/libipa/lsc_polynomial.h | 19 +++++--- src/ipa/libipa/lsc_table.cpp | 100 +++----------------------------------- src/ipa/libipa/lsc_table.h | 90 ++++++++++++++++++++++++++-------- src/ipa/rkisp1/algorithms/lsc.cpp | 4 +- src/ipa/rkisp1/algorithms/lsc.h | 3 +- 8 files changed, 125 insertions(+), 144 deletions(-) diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index d48336b169f4..6dc76b6572b3 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -53,6 +53,9 @@ public: template class LscAlgorithm : public LscAlgorithmBase { +private: + using T = typename U::QuantizedType; + public: int init(const ValueNode &tuningData, ControlInfoMap::Map &controls, const LscDescriptor &descriptor) @@ -94,7 +97,7 @@ public: const std::vector &yPos) { LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop; - lsc::ComponentsMap lscData = + lsc::ComponentsMap lscData = impl_->sampleForCrop(analogCrop, xPos, yPos); /* @@ -112,20 +115,20 @@ public: return 0; } - const lsc::Components interpolateComponents(unsigned int ct) + const lsc::Components interpolateComponents(unsigned int ct) { return sets_.getInterpolated(ct); } - const lsc::ComponentsMap getComponents() + const lsc::ComponentsMap getComponents() { return lscData_; } private: std::unique_ptr> impl_; - Interpolator sets_; - lsc::ComponentsMap lscData_; + Interpolator> sets_; + lsc::ComponentsMap lscData_; bool polynomial_; }; diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index dd2e1128a546..235f11e8c53b 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -32,25 +32,28 @@ namespace ipa { namespace lsc { /** - * \typedef Components + * \class Components * \brief Associate a colour components with a list of gains + * \tparam T The type used to store the gain values when loaded from tuning file * * 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. + * identifier for the colour component to a list of gains of type \a T. * - * Each key name shall match an entry in the tuning file. + * Each key name shall match an entry in the tuning file and the type \a T + * shall match the size of the registers where gains are stored. * * The list of keys is provided to the LscAlgorithm class using \a * LscDescriptor::keys. */ /** - * \typedef ComponentsMap + * \class ComponentsMap * \brief Associate a colour temperature to a LSC table + * \tparam T The type used to store the gain values when loaded from tuning file * * An LSC table is generated during the tuning phase for a specific light * temperature, and a tuning file usually contains LSC tables generated for @@ -61,10 +64,10 @@ namespace lsc { #ifndef __DOXYGEN__ template<> -void Interpolator:: - interpolate(const lsc::Components &a, - const lsc::Components &b, - lsc::Components &dest, +void Interpolator>:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, double lambda) { for (auto const &[k, v] : a) diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 721fd137ef21..4edcb2637d1b 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -26,8 +26,15 @@ namespace ipa { namespace lsc { -using Components = std::map>; -using ComponentsMap = std::map; +template +class Components : public std::map> +{ +}; + +template +class ComponentsMap : public std::map> +{ +}; } /* namespace lsc */ @@ -43,10 +50,10 @@ void interpolateVector(const std::vector &a, const std::vector &b, } template<> -void Interpolator:: - interpolate(const lsc::Components &a, - const lsc::Components &b, - lsc::Components &dest, +void Interpolator>:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, double lambda); #endif /* __DOXYGEN__ */ @@ -66,7 +73,7 @@ public: virtual int parseLscData(const ValueNode &tuningData, const LscDescriptor &descriptor) = 0; - virtual lsc::ComponentsMap + virtual lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, std::vector xPos, std::vector yPos); }; diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index 04d7465b3af0..a84a736734e1 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -67,6 +67,9 @@ protected: template class LscPolynomial : public LscPolynomialBase, public LscImplementation { +private: + using T = typename U::QuantizedType; + public: int parseLscData(const ValueNode &yamlSets, const LscDescriptor &descriptor) override @@ -74,14 +77,14 @@ public: return LscPolynomialBase::parseLscData(yamlSets, descriptor); } - lsc::ComponentsMap + lsc::ComponentsMap sampleForCrop(const Rectangle &cropRectangle, std::vector xPos, std::vector yPos) override { - lsc::ComponentsMap components; + lsc::ComponentsMap components; for (const auto &[t, c] : lscData_) { - lsc::Components comp; + lsc::Components comp; for (const auto &[k, p] : c) { comp.emplace(std::piecewise_construct, @@ -97,17 +100,17 @@ public: } 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) { 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; + std::vector samples; samples.reserve(xPositions.size() * yPositions.size()); diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp index 0e8f144cb873..4c9ab587d014 100644 --- a/src/ipa/libipa/lsc_table.cpp +++ b/src/ipa/libipa/lsc_table.cpp @@ -13,99 +13,6 @@ LOG_DEFINE_CATEGORY(LscTable) namespace ipa { -/** - * \class LscTableBase - * \brief Base class for LscTable - * - * Base class for LscTable for non-templated functions. - */ - -/** - * \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 LscTableBase::parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor) -{ - for (const auto &set : sets.asList()) { - uint32_t ct = set["ct"].get(0); - - int ret = parseLscComponent(set, ct, descriptor); - if (ret) - return ret; - } - - if (lscData_.empty()) { - LOG(LscTable, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; -} - -int LscTableBase::parseLscComponent(const ValueNode &yamlSet, - unsigned int ct, const LscDescriptor &descriptor) -{ - lsc::Components component; - for (auto &k : descriptor.keys) { - auto [it, inserted] = component.emplace( - std::piecewise_construct, - std::forward_as_tuple(k.c_str()), - std::forward_as_tuple(parseTable(yamlSet, - k.c_str(), - descriptor.numHCells, - descriptor.numVCells))); - if (!inserted || it->second.empty()) { - LOG(LscTable, Error) - << "Set " << k << " for color temperature " - << ct << " is missing"; - return -EINVAL; - } - } - - auto [it, inserted] = lscData_.emplace(ct, component); - if (!inserted) { - LOG(LscTable, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } - - return 0; -} - -std::vector LscTableBase::parseTable(const ValueNode &tuningData, - const char *prop, - unsigned int numHCells, - unsigned int numVCells) -{ - unsigned int lscNumSamples = numHCells * numVCells; - - std::vector table = - tuningData[prop].get>().value_or(utils::defopt); - if (table.size() != lscNumSamples) { - LOG(LscTable, Error) - << "Invalid '" << prop << "' values: expected " - << lscNumSamples - << " elements, got " << table.size(); - return {}; - } - - return table; -} - -/** - * \var LscTableBase::lscData_ - * \brief The tabular LSC data - * - * Maps colour temperatures to per-colour channel vector of gains - */ - /** * \class LscTable * \brief Table based LSC algorithm implementation @@ -119,8 +26,13 @@ std::vector LscTableBase::parseTable(const ValueNode &tuningData, /** * \fn LscTable::parseLscData() + * \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. * - * \copydoc LscTableBase::parseLscData() + * \return 0 on success or a negative error number otherwise */ } /* namespace ipa */ diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index 65e04b09c3ff..fe4e133060a6 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -23,34 +23,33 @@ LOG_DECLARE_CATEGORY(LscTable) namespace ipa { -class LscTableBase +template +class LscTable : public LscImplementation { -protected: - int parseLscData(const ValueNode &sets, - const LscDescriptor &descriptor); - private: - int parseLscComponent(const ValueNode &yamlSet, - unsigned int ct, const LscDescriptor &descriptor); - std::vector parseTable(const ValueNode &tuningData, - const char *prop, - unsigned int numHCells, - unsigned int numVCells); -protected: - lsc::ComponentsMap lscData_; -}; + using T = typename U::QuantizedType; -template -class LscTable : public LscTableBase, public LscImplementation -{ public: int parseLscData(const ValueNode &sets, const LscDescriptor &descriptor) override { - return LscTableBase::parseLscData(sets, descriptor); + for (const auto &set : sets.asList()) { + uint32_t ct = set["ct"].get(0); + + int ret = parseLscComponent(set, ct, descriptor); + if (ret) + return ret; + } + + if (lscData_.empty()) { + LOG(LscTable, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; } - lsc::ComponentsMap + lsc::ComponentsMap sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, [[maybe_unused]] std::vector xPos, [[maybe_unused]] std::vector yPos) override @@ -59,6 +58,59 @@ public: << "Tabular LSC data doesn't support resampling"; return lscData_; } + +private: + int parseLscComponent(const ValueNode &yamlSet, + unsigned int ct, const LscDescriptor &descriptor) + { + lsc::Components component; + for (auto &k : descriptor.keys) { + auto [it, inserted] = component.emplace( + std::piecewise_construct, + std::forward_as_tuple(k.c_str()), + std::forward_as_tuple(parseTable(yamlSet, + k.c_str(), + descriptor.numHCells, + descriptor.numVCells))); + if (!inserted || it->second.empty()) { + LOG(LscTable, Error) + << "Set " << k << " for color temperature " + << ct << " is missing"; + return -EINVAL; + } + } + + auto [it, inserted] = lscData_.emplace(ct, component); + if (!inserted) { + LOG(LscTable, Error) + << "Multiple sets found for color temperature " + << ct; + return -EINVAL; + } + + return 0; + } + + std::vector parseTable(const ValueNode &tuningData, + const char *prop, unsigned int numHCells, + unsigned int numVCells) + { + unsigned int kLscNumSamples = numHCells * numVCells; + + 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; + } + + lsc::ComponentsMap lscData_; }; } /* namespace ipa */ diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp index 9db67197ad64..7fa495acf10a 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -176,7 +176,7 @@ 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) { const auto &r = set.at("r"); std::copy(r.begin(), r.end(), &config.r_data_tbl[0][0]); @@ -235,7 +235,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, setParameters(*config); - const lsc::Components &set = lscAlgo_.interpolateComponents(quantizedCt); + const lsc::Components &set = lscAlgo_.interpolateComponents(quantizedCt); copyTable(*config, set); lastAppliedCt_ = ct; diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h index 31156f56cd21..eb32997d64bc 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -42,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 &set0); std::vector parseSizes(const ValueNode &tuningData, const char *prop); std::vector sizesListToPositions(Span sizes); From patchwork Wed Jul 8 09:25:19 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27297 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 6FFF5C333D for ; Wed, 8 Jul 2026 09:25:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 15BA0660D8; Wed, 8 Jul 2026 11:25:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="iu5xNLwM"; 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 D15AA660A2 for ; Wed, 8 Jul 2026 11:25:14 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 42EA83FD4; Wed, 8 Jul 2026 11:24:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502665; bh=meGdcwZyy3+EywvA9mHqxiAiLqYzviZC14BmcAakpzk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=iu5xNLwM4OH94BnDYh9rCFkdD9p6ZOJJBVAcUOQtUsHRLbryoe0zwHEiNdJzZtzAJ Fmq8NqKy+0+Q06AIN5JodQCWY7/AOn5q7zbfiNEHurFdDPhmfv5FfNUPq4/QI3mtwo iJO1aKcNHRMFQ1IIIEa8EaOc+XyJx2vReeXpwxic= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:19 +0200 Subject: [PATCH v4 32/35] ipa: libipa: lsc: Rename sampleForCrop to resampleLscData MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-32-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=4426; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=meGdcwZyy3+EywvA9mHqxiAiLqYzviZC14BmcAakpzk=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdwb6tZ9q2McfZLJvbJHbe41sW0bUTN1gVan 9CzinBJsBiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XcAAKCRByNAaPFqFW PJBmEAC1Rob3rp3W1PEK4zgW8lAR5ytNr/pYx14IP/qRx0Y3oNqsL+eQQxmJaRKKpTcN2zt6WlM s+B/lZTD2/skk0SDSVH6Fvyb4OYOAol51cxvAmraW1qo9x/Sio1tavWEbnexPZaTTakj6JuSBmj pUYgqIXrmpEGKQ5wkCCZPMXtTkrOIdEijKiu0B2OlQwi24lokmOBffqLxx6Cr0oOnhEEGJXVoql HS2+/Jq3jc9bwRkqTLf8IRWK7Xh356BE2o/uYL6QFzLB6x7D+p0ypWODZ4Hq6YXi6HjyQafDGJy Pxwdwh8KzHok+VLvfXHnQVrC7MhxH19pL1gtPjUMZ0E9sZfTwMhAXtFuni+ytLPRpce817GTkTy 4xq2XhJiwgIrc07Z8aKcY5xRUgQeGmwl47Cm6o+t4T/7r9BVHb1EO83//YYv2FQcQkL1oiKoftg 0vK6hwNE/SZY6hT0raCnh7JC/4Eecx9DVUQEeWQGC25amDm1+dGnw5D/iFpPLtuNwwojdE4Kl0P MXQNutwTpuhl/fNCOPtudYOs78yee4jPw1CBgy5kKURtUxAnzm+n+29/8CtUOMKj9XQHDQ5rJc+ OIA00W6zQPBvTN7o/cFY6jR/K9QHR/OtouyJUNPE44Q7bomBti0DUAVhadhO4UyEA83jnuGxbkz 02NbeMB/v2S6Q5A== 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" Rename the function to a more precise name. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 2 +- src/ipa/libipa/lsc.h | 2 +- src/ipa/libipa/lsc_base.cpp | 2 +- src/ipa/libipa/lsc_base.h | 4 ++-- src/ipa/libipa/lsc_polynomial.cpp | 2 +- src/ipa/libipa/lsc_polynomial.h | 4 ++-- src/ipa/libipa/lsc_table.h | 6 +++--- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp index f5646937c60a..0ce40c09caed 100644 --- a/src/ipa/libipa/lsc.cpp +++ b/src/ipa/libipa/lsc.cpp @@ -269,7 +269,7 @@ void LscAlgorithmBase::process(lsc::FrameContext &context, ControlList &metadata * 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 + * \sa LscImplementation::resampleLscData * * \return 0 on success, a negative error code otherwise */ diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h index 6dc76b6572b3..1f46555f9bf0 100644 --- a/src/ipa/libipa/lsc.h +++ b/src/ipa/libipa/lsc.h @@ -98,7 +98,7 @@ public: { LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop; lsc::ComponentsMap lscData = - impl_->sampleForCrop(analogCrop, xPos, yPos); + impl_->resampleLscData(analogCrop, xPos, yPos); /* * Retain a copy of the components table. diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index 235f11e8c53b..c87e2680d973 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -127,7 +127,7 @@ void Interpolator>:: */ /** - * \fn LscImplementation::sampleForCrop + * \fn LscImplementation::resampleLscData * \brief Re-sample the LSC components for \a cropRectangle * \param[in] cropRectangle The sensor analogue crop rectangle * \param[in] xPos List of horizontal positions of the LSC grid nodes diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index 4edcb2637d1b..a25d951e8ddb 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -74,8 +74,8 @@ public: const LscDescriptor &descriptor) = 0; virtual lsc::ComponentsMap - sampleForCrop(const Rectangle &cropRectangle, - std::vector xPos, std::vector yPos); + resampleLscData(const Rectangle &cropRectangle, + 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 7d4381973646..b88048e873d9 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -190,7 +190,7 @@ int LscPolynomialBase::parseLscData(const ValueNode &sets, */ /** - * \fn LscPolynomial::sampleForCrop() + * \fn LscPolynomial::resampleLscData() * \brief Re-sample the LSC components for \a cropRectangle * \param[in] cropRectangle The sensor analogue crop rectangle * \param[in] xPos List of horizontal positions of the LSC grid nodes diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index a84a736734e1..00bbd068d537 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -78,8 +78,8 @@ public: } lsc::ComponentsMap - sampleForCrop(const Rectangle &cropRectangle, - std::vector xPos, std::vector yPos) override + resampleLscData(const Rectangle &cropRectangle, + std::vector xPos, std::vector yPos) override { lsc::ComponentsMap components; diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h index fe4e133060a6..c3d6c6b95dcd 100644 --- a/src/ipa/libipa/lsc_table.h +++ b/src/ipa/libipa/lsc_table.h @@ -50,9 +50,9 @@ public: } lsc::ComponentsMap - sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle, - [[maybe_unused]] std::vector xPos, - [[maybe_unused]] std::vector yPos) override + resampleLscData([[maybe_unused]] const Rectangle &cropRectangle, + [[maybe_unused]] std::vector xPos, + [[maybe_unused]] std::vector yPos) override { LOG(LscTable, Warning) << "Tabular LSC data doesn't support resampling"; From patchwork Wed Jul 8 09:25:20 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27298 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 0CC0FC3301 for ; Wed, 8 Jul 2026 09:25:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B4BAE660C5; Wed, 8 Jul 2026 11:25:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YGEEPmFd"; 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 21849660AD for ; Wed, 8 Jul 2026 11:25:15 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7E6471D18; Wed, 8 Jul 2026 11:24:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502665; bh=pKxxZ9cajwGg/4XFTEBKJ35/7Bmlg5h/GcltukR9A5Y=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=YGEEPmFdGlxy0Ok/QbREk3oQ/9s/GuDvefSfzdKaAVjAD+9bGOwUYTiAvFyPLwdYx x0MK+QqGYzHylmL0QO4LnJeem1B2uJ/HzAPSl3VwQKKZcAHz0xd3t30y4zvvP6J9kr E6nakEk0lY+hvDCJB5Zissid0b+PyfCEyuj+zYqg= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:20 +0200 Subject: [PATCH v4 33/35] ipa: libipa: lsc: Specialize Interpolator for uint8_t MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-33-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1736; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=pKxxZ9cajwGg/4XFTEBKJ35/7Bmlg5h/GcltukR9A5Y=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdwvs+Wosshqjm/oUrexR4ZOqhR05t/X9loF Fmhr3P57iiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XcAAKCRByNAaPFqFW PNKED/9gm2zF7iPWE86wDPBPsSVZ2JlVfX2ObVp96xxFeNk8Y4+UdVwXleG/tjfmS9luHwxaPcY W3pPI+Ea42Nggyr/QYqyrDJszzrkReHsmt90E0M6RKrTmu/EHZXEJ8eCy+XsZ21GAXt/E4Zredf +DKjknXRpOhU6+A4fBaXhRP/9sU+p+N2m/0Y2iTVfwvdJdbllu3cmIAcAmh1o2EGLdd5puGK7iy jZsCRalxHpXIbTm6WzZPHXNys2y3fVTmmjS3lswRg+rXAcqyg9lQKeVI+ngtKoZjHK+dCVhmm9s cnq68YXHHNjvA0SaJHfzJ3zQ3idp+m1+m09qyXTmTIUYXzlsZ2ZsPXE5Py1jOyH9qNnteRobWsY cCJCByrgHNWUKHg907zJinWSNo2IPSFYDYnMpo5IpNe1oZKDbRNUJ9vB6mBUqXGrTTMoSpQ8p+N yg6EUBrZYNXUxIF7CPNq8nndsIGKb39ZmA3QsI1T1tqxlBVvrtE1ihYn9XLIMIRYYgDeGV9yTDf cdTlQqmkZNALO23NW+6cmpXjViSvlNXbpkXWoOBkfzIRUrzU7np8chtTP7nrrnsYFse8Tamq+b4 TOGWrIZbzWCeaf+NaXi5PI3gqVWn0+cYa0HMMuVJyi5gywihFkTy5+JrnDgK+pTsX21/G0M/fEp ui16eY8ZuVnXTgA== 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 Mali-C55 LSC algorithm uses 8-bit gain values. Specialize Interpolator> for uint8_t to prevent the uint16_t specialization from being selected. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc_base.cpp | 11 +++++++++++ src/ipa/libipa/lsc_base.h | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp index c87e2680d973..a0b78ac5265b 100644 --- a/src/ipa/libipa/lsc_base.cpp +++ b/src/ipa/libipa/lsc_base.cpp @@ -63,6 +63,17 @@ namespace lsc { } /* 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); +} + template<> void Interpolator>:: interpolate(const lsc::Components &a, diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h index a25d951e8ddb..51f3dd91f547 100644 --- a/src/ipa/libipa/lsc_base.h +++ b/src/ipa/libipa/lsc_base.h @@ -49,6 +49,13 @@ void interpolateVector(const std::vector &a, const std::vector &b, 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); + template<> void Interpolator>:: interpolate(const lsc::Components &a, From patchwork Wed Jul 8 09:25:21 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27299 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 AB1CFC333E for ; Wed, 8 Jul 2026 09:25:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4D450660EE; Wed, 8 Jul 2026 11:25:45 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="i3s1sHfG"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6E81F660AF for ; Wed, 8 Jul 2026 11:25:15 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BCFAE3FE8; Wed, 8 Jul 2026 11:24:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502665; bh=ax8BgGImoAq8vCACjcTubXq3ibzUonWpyk48NOKhiQI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=i3s1sHfGHGZ88HBjafAQ48Ebng/9I7lNgltMabenQ+zz6W/EbVZmy5E3VvWZ5aS84 R9Lm1jN7mbAvYeIk6+j+bIpUST0WtVNOCgbHaZu8/AG9w+yZeD6Vv2/zDbl+7dtKgu RxpNSpPi198DCUpU9EvDjSWxoaKNY1cMIyJPDkO8= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:21 +0200 Subject: [PATCH v4 34/35] ipa: mali-c55: Add sensorInfo to IPAContext MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-34-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1573; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=ax8BgGImoAq8vCACjcTubXq3ibzUonWpyk48NOKhiQI=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdwNFTeZzLATDVdllkaK4yknYZu6LiFYVNEU 8PuWbVnnzCJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XcAAKCRByNAaPFqFW PNdNEACP6nvqbGVKuq9mCCi+jJ74Stq7zFJB2QYX7JsDk4/Ow95hURfnulDX6WmwtHzgqjnKBcd 8r3/RWNFjLL/N3UYHZBeGBjAlBYWCMWjQNOqOFLylhkeTu6b9FK/jcpJRlVZYM2O/RAJ95usnVV tc6x2xg8Iy0q6NfOIhQr3IcreRkBbL/1/49MskgVfv6MY04rAUD54WAk/zAbLxCLn6cE/Qelx44 zvuxA9EaS8ltFImgEBj24U1kUjYWiJom+hTze5E7zW9Cx1PTLDGadkroYTb47zUri31iQVGY/px i9u9SU3kjrVbd6PUcJe41Itnuwsn6kru0Gfe0WEzUKcTczKO6HYfVX8xZ2ufOY8hIWlirN/FigB yyofFvdhpwIqmKPL71G76m61msv1HX2XDdxUVFLiWnON51TzKf5K/q7agiQD1bY9HH3g5RdmWg/ YR01uI3IjFFhucLuWacJc6ydqwZCPFeXLiq01PmXZuAalQ6ED9kjs+XazzqKVdv5jUTYZAkIDUD iHiLcbjkEvCvUa+RZYrmZghd3BCtizyzrZtmqhbZp/e///8loSXzJaU/xnqP7PiE/Ye+BP1mY9x Ee1DB2ylT1z2SRzwge1BvImBd7r8i419RALmvZdSD0fW3QznF6zOAYilfiiWYEPaxZgvuSKoZ5W Ab9DAKwFebqmtWQ== 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 Jul 8 09:25:22 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27300 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 55202C333F for ; Wed, 8 Jul 2026 09:25:46 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 095FB660CB; Wed, 8 Jul 2026 11:25:46 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="k7lReckP"; 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 C3083660B0 for ; Wed, 8 Jul 2026 11:25:15 +0200 (CEST) Received: from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it [93.46.82.201]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 14D5E3FF5; Wed, 8 Jul 2026 11:24:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783502666; bh=gBpwl50taYAIJtQ3lC8VXTG531PwJCLIUQHX6s44Emo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=k7lReckPl+GYkh8PjzYsc8plvbhjKmTLWGbsbVAQgRibWnOsmXZ8mc2epxX43UHyE jsmwHIX+kziZzlpWY+syI97yNC95YCM7Z714NmdxALG+wIDK+q2wma+qrUhQjDMZKF V//ou9+peLWPF0GCmmjBOtT3OO7EdKnrFe84wkqY= From: Jacopo Mondi Date: Wed, 08 Jul 2026 11:25:22 +0200 Subject: [PATCH v4 35/35] ipa: mali-c55: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260708-libipa-algorithms-v4-35-f0e99f035294@ideasonboard.com> References: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> In-Reply-To: <20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com> To: libcamera-devel@lists.libcamera.org, Stefan Klug Cc: Jacopo Mondi , Kieran Bingham , Daniel Scally X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=10843; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=gBpwl50taYAIJtQ3lC8VXTG531PwJCLIUQHX6s44Emo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqThdwE7lNEMdp7FrhENF4jfqqiwtU4d2xKTA6M qSdrJS3Qs+JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak4XcAAKCRByNAaPFqFW PPMREACg5YNo1ZaqUxvz4kRFE0fh2zZJutCK0hRGQlLC5ynATU9ixi2egDz6ZaI3TE2xreK6lbF Cx6sFSC/ffKZJVNSOmMFsK/i+vLmIhLqHFeso57Rx22DkzCAWWA+vYDwiEqUwyAtlUC6TacbBl7 JIKGJMsAOrtK+y5YEIQAJMXkkrgBummvQCCSVFisI6ldJKN50RKpGZYdDc50VYeMnByKN/QFRl5 RC5etmKl68lH4PmzBBxeZ7yr4kdC2DwCd68GNASwdAmbHYNc9oBHIvjT9Ns8vH7ElZIpzTjaYYi h3OZnVfSVs02v5svYWMPPhrWUdYkWuA4R2TQJS6BYbVFEjQYfv0YUDCYIKlNbauiH+ygTl/KFhg p8LPjMGZtb4x75/GWrCSJ1mOIZkIyv52lx1ihnPV9PKrUMCsCcZGFGkBG3681WWU5SzUGkUzFA6 T8erJcTigg6CSPMLFEMsG6Vm36A++9t9OEXm4TU20hQ9dYB2H/c5vMXg/l3VnbKvWPjeqhHGeF3 EXwJ0454OVsNadQ76pslDeH/QzAcWRfxYtaquoJk1aTehPZYu5VAs+AkmP4eXybxmzJxmgYpoWm NSDdNZZmK+KlrWlVs0qDjBHQQfAy7kt6fSTnxTBSyPO7uyXCYBE5Ro950LngRs0Q/gc1MZf7wLX oblv7gu0dPK7FRA== 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. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham Reviewed-by: Daniel Scally --- src/ipa/mali-c55/algorithms/lsc.cpp | 156 ++++++++++++++++++++++-------------- src/ipa/mali-c55/algorithms/lsc.h | 32 ++++++-- src/ipa/mali-c55/ipa_context.h | 3 + 3 files changed, 122 insertions(+), 69 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp index 097239dcef3e..36b422939e43 100644 --- a/src/ipa/mali-c55/algorithms/lsc.cpp +++ b/src/ipa/mali-c55/algorithms/lsc.cpp @@ -9,38 +9,74 @@ #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" }, + .numHCells = kMeshSize, + .numVCells = 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. + */ + const auto &components = lscAlgo_.getComponents(); + for (auto const &[ct, component] : components) { if (std::count(colourTemperatures_.begin(), colourTemperatures_.end(), ct)) { LOG(MaliC55Lsc, Error) @@ -48,44 +84,29 @@ 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); - - /* - * 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(); - } + std::vector rTable = component.at("r"); + std::vector gTable = component.at("g"); + std::vector bTable = component.at("b"); - if (rTable.size() != tableSize || - gTable.size() != tableSize || - bTable.size() != tableSize) { + /* Only 32x32 tables of coefficients are accepted. */ + if (rTable.size() != kGridSize || gTable.size() != kGridSize || + bTable.size() != kGridSize) { LOG(MaliC55Lsc, Error) - << "Invalid or mismatched table size for colour temperature " << ct; + << "Invalid table size for colour temperature " << ct; return -EINVAL; } - 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 +118,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 +220,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 {