From patchwork Mon Jul 6 08:01: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: 27201 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 1ABF5C3308 for ; Mon, 6 Jul 2026 08:02:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 255E965EFF; Mon, 6 Jul 2026 10:02:00 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZcyT58iH"; 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 AA53C65EFF for ; Mon, 6 Jul 2026 10:01:57 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9ED2E16F9; Mon, 6 Jul 2026 10:01:08 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324869; bh=T1+9aPkNpAvIevTiICbsnSYdxSzS36sKLICeNpG/+RE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ZcyT58iHDoC6iZI/V6x0bahfA/I6HUqIS5hZM6/Zd9dsPOkuQrH49K+DLxW/2PATu JbGRCGs62ZGtkydnKsNl/dFzDsRVYGfbnhoSWIy3EpJEa0RyJAdDN9SQFUbTXVnNAK vVQoozefwRnM/wzjli6AqXqdbLp40dJ4NKDW7AJI= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:21 +0200 Subject: [PATCH v3 01/35] ipa: libipa: awb: Reimplement AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-1-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=51483; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=T1+9aPkNpAvIevTiICbsnSYdxSzS36sKLICeNpG/+RE=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DuTiFp73SZj5CqWVO3wT+IA3b3weKBqyxXt LvFcodfkkaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg7gAKCRByNAaPFqFW PJefD/42ZrZ32X0+pxSTfAZ05B0KzRmPHE6eGgqzsa3cuxAe1+plpM1g9FXW1P75EcLNliJ00FQ jedgTmCKfAmBsHWYYRPvBc1BJab+JQofWgVFBg9t09zyqUYqSJTueojzw0ifKnlijCqQq0kazC1 fFMdZ7hMkvU096CG4GY+1vyHTwf4Q2QFAj+aTW3dbvA4Gtt2WihDJLGIweePsE9DeCv5tyVhotX nDaFx2BvePNpLCNlTFMMQOb83jjuZQpJHuA6IpCmkLysQmPb23rp0si+7frjKE/rFJSmewohfKT GBKhjehZa54+ehlIyWqr/P2mtey17CnrOz9s3UEtf+GojNIgrR0/KZlshLI6wj9mjU2P4jd0CS/ XahCNcnaNoUzXWuocEExefuZft8as1tTs7ekUEzObud4R82OXt7YB9SR6csvHsSw9r5K9qljsoO x/ITPCdinqlEHVaGpk3WJPpn8eCcwqEPte7AgO8NOXCqPO/9IzvEKSDOMrp815Lz3kk+FbvnW26 XKovvchixDSlBOQq2PD8i88sNIWszJN/pGn4h1ZmhLUdseMkp+h4dfIQkGE7ICzzPcTZs7embfG Ejkr1DJ+UQb68DmnhUGRyId4i09GlWQzU/iLCWDFUvb+cyRIhaAMocCet2U25dqwtTUFU9mcADR QUUvUcoLGY4QAlA== 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 --- src/ipa/libipa/awb.cpp | 472 ++++++++++++++++++++++++++++++++------ src/ipa/libipa/awb.h | 109 +++++++-- src/ipa/libipa/awb_bayes.cpp | 52 ++--- src/ipa/libipa/awb_bayes.h | 12 +- src/ipa/libipa/awb_grey.cpp | 25 +- src/ipa/libipa/awb_grey.h | 6 +- src/ipa/rkisp1/algorithms/awb.cpp | 360 +++++++++-------------------- src/ipa/rkisp1/algorithms/awb.h | 18 +- src/ipa/rkisp1/ipa_context.h | 29 +-- 9 files changed, 672 insertions(+), 411 deletions(-) diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp index 29a78069c52c..95291840d8fb 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,60 @@ 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::temperatureK + * \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 + * \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 shall provide a derived implementation of this class to give the + * awb algorithm access to the hardware-specific statistics data in a common + * format. + */ + +/** + * \fn AwbStats::AwbStats() + * \brief Construct an empty AwbStat + */ + +/** + * AwbStat::~AwbStat + * \brief Virtual class destructor */ /** @@ -57,7 +94,7 @@ namespace ipa { * applied. To keep the actual implementations computationally inexpensive, * the squared colour error shall be returned. * - * If the AWB statistics provide multiple zones, the average of the individual + * If the awb statistics provide multiple zones, the average of the individual * squared errors shall be returned. Averaging/normalizing is necessary so that * the numeric dimensions are the same on all hardware platforms. * @@ -65,79 +102,328 @@ namespace ipa { */ /** - * \fn AwbStats::rgbMeans() - * \brief Get RGB means of the statistics + * \fn AwbStats::valid() + * \brief Retrieve if the awb statistics are valid * - * Fetch the RGB means from the statistics. The values of each channel are - * 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. + * 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 Retrieve the RGB means values + * + * Fetch the RGB means from the statistics in the [0, 1] range. * * \return The RGB means */ /** - * \class AwbAlgorithm - * \brief A base class for auto white balance algorithms + * \var AwbStats::rgbMeans_ + * \brief Mean values of colour channels + */ + +/** + * \var AwbStats::rg_ + * \brief Red/Green ratio + */ + +/** + * \var AwbStats::bg_ + * \brief Blue/Green ratio + */ + +/** + * \class AwbImplementation + * \brief Pure virtual base class for awb algorithms implementations + * + * The AwbImplementation class defines the interface for the awb algorithm + * implementations. * - * 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. + * 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. + * + * 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::temperatureK + * \brief The computed colour temperature, in Kelvin */ /** - * \fn AwbAlgorithm::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 + * \fn AwbImplementation::~AwbImplementation + * \brief Virtual class destructor + */ + +/** + * \fn AwbImplementation::init() + * \param[in] tuningData + * \brief Initialize the algorithm by parsing \a tuningData + */ + +/** + * \fn AwbImplementation::calculateAwb() + * \param[in] stats The awb statistics + * \param[in] lux The estimated lux level + * \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 - * it. + * Calculate a new set of colour gains and a colour temperature given a new + * set of statistics \a stats, an estimated luminance \a lux and a range of + * colour temperature to limit the search. * - * \return The AWB result + * \return A Result computed using the new statistics + */ +/** + * \fn AwbImplementation::gainsFromColourTemperature() + * \param[in] temperatureK Colour temperature in Kelvin + * + * Calculate a new set of colour gains for a new manually specified colour + * temperature \a temperatureK. + * + * \return The RGB gain values adjusted to \a temperatureK or std::nullopt + */ + +/** + * \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. */ /** - * \fn AwbAlgorithm::gainsFromColourTemperature() - * \brief Compute white balance gains from a colour temperature - * \param[in] colourTemperature The colour temperature in Kelvin + * \brief Initialize the algorithm with the given tuning data + * \param[in] tuningData The tuning data to use for the algorithm * - * Compute the white balance gains from a \a colourTemperature. 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. + * 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 The colour gains or std::nullopt if the conversion is not possible + * \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.temperatureK = kDefaultColourTemperature; + state.automatic.temperatureK = kDefaultColourTemperature; + + return 0; +} /** - * \fn AwbAlgorithm::controls() - * \brief Get the controls info map for this algorithm + * \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.temperatureK = *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.temperatureK = state.manual.temperatureK; +} /** - * \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.temperatureK = state.automatic.temperatureK; + } +} /** + * \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.temperatureK; + ct = ct * speed + state.automatic.temperatureK * (1 - speed); + + state.automatic.temperatureK = awbResult.temperatureK; + 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.temperatureK); + + LOG(Awb, Debug) << std::showpoint << "Means " << stats.rgbMeans() + << ", gains " << state.automatic.gains + << ", temp " << state.automatic.temperatureK << "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 +442,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 +521,83 @@ int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData, } controls_[&controls::AwbMode] = ControlInfo(availableModes, def); + currentMode_ = &modes_[controls::AwbAuto]; return 0; } /** - * \class AwbAlgorithm::ModeConfig - * \brief Holds the configuration of a single AWB mode + * \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. * - * AWB modes limit the regulation of the AWB algorithm to a specific range of - * colour temperatures. + * The min and max gain values are initialized by AwbAlgorithm::init(). */ /** - * \var AwbAlgorithm::ModeConfig::ctLo + * \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. Use by AwbBayes only. + */ + +/* + * \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..72f46367a136 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,118 @@ namespace libcamera { namespace ipa { -struct AwbResult { +namespace awb { + +struct Context { RGB gains; - double colourTemperature; + unsigned int temperatureK; }; +struct ActiveState { + Context manual; + Context automatic; + + bool autoEnabled; +}; + +struct FrameContext : public Context { + bool autoEnabled; +}; + +} /* namespace awb */ + struct AwbStats { + AwbStats() = default; + virtual ~AwbStats() = default; + virtual double computeColourError(const RGB &gains) const = 0; - virtual RGB rgbMeans() const = 0; + virtual bool valid() const = 0; + RGB rgbMeans() const { return rgbMeans_; } protected: - ~AwbStats() = default; + RGB rgbMeans_; + double rg_; + double bg_; }; -class AwbAlgorithm +class AwbImplementation { public: - virtual ~AwbAlgorithm() = default; + struct Result { + RGB gains; + unsigned int temperatureK; + }; + 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 temperatureK) = 0; +}; - const ControlInfoMap::Map &controls() const - { - return controls_; - } +class AwbAlgorithmBase +{ +public: + int init(const ValueNode &tuningData); - virtual void handleControls([[maybe_unused]] const ControlList &controls) {} + int configure(awb::ActiveState &state); + + 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..1e4af717f62d 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,20 +71,23 @@ 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); + result.temperatureK = estimateCCT(means); /* - * Estimate the red and blue gains to apply in a grey world. The green - * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the - * divisor to a minimum value of 1.0. + * Calculate the red and blue gains to apply in a grey world by simply + * inverting the red/green and blue/green ratios as reported in + * statistics. The green gain is hardcoded to 1.0. */ result.gains.r() = means.g() / std::max(means.r(), 1.0); result.gains.g() = 1.0; result.gains.b() = means.g() / std::max(means.b(), 1.0); + return result; } @@ -93,15 +97,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 temperatureK 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..ae9f6a0c243e 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,26 +23,16 @@ 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: - RkISP1AwbStats(const RGB &rgbMeans) - : rgbMeans_(rgbMeans) + RkISP1AwbStats(const IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats) { + calculateRgbMeans(frameContext, stats); + rg_ = rgbMeans_.r() / rgbMeans_.g(); bg_ = rgbMeans_.b() / rgbMeans_.g(); } @@ -65,17 +50,106 @@ public: return delta2; } - RGB rgbMeans() const override + bool valid() const override { - return rgbMeans_; + /* 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_; + void calculateRgbMeans(const IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats); + bool rgbMode_ = false; }; +void RkISP1AwbStats::calculateRgbMeans(const IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats) +{ + if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) { + LOG(RkISP1Awb, Error) << "AWB data is missing in statistics"; + return; + } + + const rkisp1_cif_isp_awb_stat *awb = &stats->params.awb; + + if (awb->awb_mean[0].cnt == 0) { + LOG(RkISP1Awb, Debug) << "AWB statistics are empty"; + return; + } + + if (rgbMode_) { + rgbMeans_ = {{ + static_cast(awb->awb_mean[0].mean_cr_or_r), + static_cast(awb->awb_mean[0].mean_y_or_g), + static_cast(awb->awb_mean[0].mean_cb_or_b) + }}; + } else { + /* Get the YCbCr mean values */ + Vector yuvMeans({ + static_cast(awb->awb_mean[0].mean_y_or_g), + static_cast(awb->awb_mean[0].mean_cb_or_b), + static_cast(awb->awb_mean[0].mean_cr_or_r) + }); + + /* + * Convert from YCbCr to RGB. The hardware uses the following + * formulas: + * + * Y = 16 + 0.2500 R + 0.5000 G + 0.1094 B + * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B + * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B + * + * This seems to be based on limited range BT.601 with Q1.6 + * precision. + * + * The inverse matrix is: + * + * [[1,1636, -0,0623, 1,6008] + * [1,1636, -0,4045, -0,7949] + * [1,1636, 1,9912, -0,0250]] + */ + static const Matrix yuv2rgbMatrix({ + 1.1636, -0.0623, 1.6008, + 1.1636, -0.4045, -0.7949, + 1.1636, 1.9912, -0.0250 + }); + static const Vector yuv2rgbOffset({ + 16, 128, 128 + }); + + rgbMeans_ = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset); + + /* + * Due to hardware rounding errors in the YCbCr means, the + * calculated RGB means may be negative. This would lead to + * negative gains, messing up calculation. Prevent this by + * clamping the means to positive values. + */ + rgbMeans_ = rgbMeans_.max(0.0); + } + + /* + * The ISP computes the AWB means after applying the CCM. Apply the + * inverse as we want to get the raw means before the colour gains. + */ + rgbMeans_ = frameContext.ccm.ccm.inverse() * rgbMeans_; + + /* + * The ISP computes the AWB means after applying the colour gains, + * divide by the gains that were used to get the raw means from the + * sensor. Apply a minimum value to avoid divisions by near-zero. + */ + rgbMeans_ /= frameContext.awb.gains.max(0.01); +} + +/** + * \class Awb + * \brief Manage the white balance with automatic and manual controls + */ + Awb::Awb() : rgbMode_(false) { @@ -86,40 +160,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 +169,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 +188,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 +202,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,136 +271,12 @@ 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; - } - - const rkisp1_cif_isp_stat *params = &stats->params; - const rkisp1_cif_isp_awb_stat *awb = ¶ms->awb; + RkISP1AwbStats awbStats(frameContext, stats); - if (awb->awb_mean[0].cnt == 0) { - LOG(RkISP1Awb, Debug) << "AWB statistics are empty"; - 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{ 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 -{ - Vector rgbMeans; - - if (rgbMode_) { - rgbMeans = {{ - static_cast(awb->awb_mean[0].mean_cr_or_r), - static_cast(awb->awb_mean[0].mean_y_or_g), - static_cast(awb->awb_mean[0].mean_cb_or_b) - }}; - } else { - /* Get the YCbCr mean values */ - Vector yuvMeans({ - static_cast(awb->awb_mean[0].mean_y_or_g), - static_cast(awb->awb_mean[0].mean_cb_or_b), - static_cast(awb->awb_mean[0].mean_cr_or_r) - }); - - /* - * Convert from YCbCr to RGB. The hardware uses the following - * formulas: - * - * Y = 16 + 0.2500 R + 0.5000 G + 0.1094 B - * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B - * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B - * - * This seems to be based on limited range BT.601 with Q1.6 - * precision. - * - * The inverse matrix is: - * - * [[1,1636, -0,0623, 1,6008] - * [1,1636, -0,4045, -0,7949] - * [1,1636, 1,9912, -0,0250]] - */ - static const Matrix yuv2rgbMatrix({ - 1.1636, -0.0623, 1.6008, - 1.1636, -0.4045, -0.7949, - 1.1636, 1.9912, -0.0250 - }); - static const Vector yuv2rgbOffset({ - 16, 128, 128 - }); - - rgbMeans = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset); - - /* - * Due to hardware rounding errors in the YCbCr means, the - * calculated RGB means may be negative. This would lead to - * negative gains, messing up calculation. Prevent this by - * clamping the means to positive values. - */ - rgbMeans = rgbMeans.max(0.0); - } - - /* - * The ISP computes the AWB means after applying the CCM. Apply the - * inverse as we want to get the raw means before the colour gains. - */ - rgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans; - - /* - * The ISP computes the AWB means after applying the colour gains, - * divide by the gains that were used to get the raw means from the - * sensor. Apply a minimum value to avoid divisions by near-zero. - */ - rgbMeans /= frameContext.awb.gains.max(0.01); - - return rgbMeans; -} REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h index 60d9ef111495..c7cb59a75bde 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_stat_buffer *stats) const; - std::unique_ptr awbAlgo_; + AwbAlgorithm> awbAlgo_; bool rgbMode_; }; 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 Mon Jul 6 08:01: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: 27202 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 3A2B7C3308 for ; Mon, 6 Jul 2026 08:02:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 11E656600A; Mon, 6 Jul 2026 10:02:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="LFq0KR5P"; 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 069BE65FF1 for ; Mon, 6 Jul 2026 10:01:58 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 805691741; Mon, 6 Jul 2026 10:01:09 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324870; bh=/3YkLrcJWuugoQsrYfXRZ88w2frQkKfadGzUcdXs9BA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=LFq0KR5Pw9ueC6rABNVh/Fy5OlGPhjDDl2cfGh0ZGcP5JpOdyeIziDwfhuczKXFKz i40RrVkDFGVKIMQ/f6Ca3Oe/uznHivOnrfy7aFN4lTbpvF+pj21Sfvvnt7+k5NN7x6 vh3UStH9ljeZ8iDbaUOaP3D/qUY2OZkPXa1Idyr4= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:22 +0200 Subject: [PATCH v3 02/35] ipa: mali-c55: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-2-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=17419; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=/3YkLrcJWuugoQsrYfXRZ88w2frQkKfadGzUcdXs9BA=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dvvpx99mB9yT85ItdCdSXoS5BKpTRt8fKV4 VvWrVXM+uGJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg7wAKCRByNAaPFqFW PJJkD/9XUkrDfRE1I8FMfOSvcUGqpEWojqJjR+oNytHn5DxxS7aMSKkekxyOymNhA9KJyi5AhLt M738l4oEUDgqTq21QcUXbshKtdJ/beb4Xg43I8UuY6t1iRt5/NICJo1gI5G4USEVkM+pXypQcoC NGZH0wjanRF3gICf8V/Qq0yYwRCOiD/F+SktQ28Wqqz7DYIgDvNOZAaKX9c4Eybiom99SahEcIu CCmWQbW64q/yWg7r5U/n8e5DQ2gJ4gmVSUPR2RC7esGMKK2PTt7TnaCEN9ISl0c3fUMPY8eWkOX wMtzJkp4Rw7rNhsNjT4RiSMsJSAAobC5imxAagOnivXvuM8+vOfXHM8bmNNA7+DzLIXSmGY4rDU ZlCUvOhLIkqJG5CIIA2YBl500An1sqNSKfoWzVYnWig8Nizbds8OG+YEbnq+i/fDkmWzAv7OpcO Zp1p0FGJs/MAUpoENEoqdi3LlOQnjfuoVfya5+WEP/NZt1iQlCLaIAgUCY+ALTWWiJVKoWU6mf0 o1k+FsP7r9UddBdde+11RLANf2o6kLiInIeCCIaYO33qs3WXMcGqCdfsBT6QYiyDHQ8CV5Y9FgM wUhHHx/uAtLfdWL/e1EbcZF/DqI4JPorivNYwxh0fw9ye6TM8OVCHrMVP7mKwA/y1CKO11+FkdJ DeM64B2v58OjPxA== 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 | 213 ++++++++++++++++++++---------------- src/ipa/mali-c55/algorithms/awb.h | 28 ++++- src/ipa/mali-c55/ipa_context.cpp | 6 + src/ipa/mali-c55/ipa_context.h | 15 ++- src/ipa/mali-c55/mali-c55.cpp | 27 ++--- 5 files changed, 166 insertions(+), 123 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp index 8a671b52be59..eb7f8c5c01cf 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,81 @@ #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; + +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; + } + + 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; + } +}; -Awb::Awb() +/** + * \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 +138,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 +172,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 +216,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..a1e4c39f3870 100644 --- a/src/ipa/mali-c55/ipa_context.cpp +++ b/src/ipa/mali-c55/ipa_context.cpp @@ -96,6 +96,12 @@ 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 + * + * \var IPAContext::camHelper + * \brief The camera sensor helper */ } /* 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..f6c6938b3cb3 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -12,8 +12,10 @@ #include "libcamera/internal/bayer_format.h" +#include #include +#include "libipa/awb.h" #include "libipa/fixedpoint.h" namespace libcamera { @@ -54,10 +56,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 +66,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 { @@ -85,6 +81,9 @@ struct IPAContext { FCQueue frameContexts; ControlInfoMap::Map ctrlMap; + + /* Interface to the Camera Helper */ + std::unique_ptr camHelper; }; } /* namespace ipa::mali_c55 */ diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 1d3af0627fdb..c35cc10bea01 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -77,9 +77,6 @@ private: ControlInfoMap sensorControls_; - /* Interface to the Camera Helper */ - std::unique_ptr camHelper_; - /* Local parameter storage */ struct IPAContext context_; }; @@ -101,8 +98,8 @@ std::string IPAMaliC55::logPrefix() const int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, ControlInfoMap *ipaControls) { - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (!camHelper_) { + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPAMaliC55, Error) << "Failed to create camera sensor helper for " << settings.sensorModel; @@ -145,10 +142,10 @@ void IPAMaliC55::setControls() if (activeState.agc.autoEnabled) { exposure = activeState.agc.automatic.exposure; - gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); + gain = context_.camHelper->gainCode(activeState.agc.automatic.sensorGain); } else { exposure = activeState.agc.manual.exposure; - gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); + gain = context_.camHelper->gainCode(activeState.agc.manual.sensorGain); } ControlList ctrls(sensorControls_); @@ -194,17 +191,17 @@ void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, context_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration; context_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration; context_.configuration.agc.defaultExposure = defExposure; - context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain); - context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain); + context_.configuration.agc.minAnalogueGain = context_.camHelper->gain(minGain); + context_.configuration.agc.maxAnalogueGain = context_.camHelper->gain(maxGain); - if (camHelper_->blackLevel().has_value()) { + if (context_.camHelper->blackLevel().has_value()) { /* * The black level from CameraSensorHelper is a 16-bit value. * The Mali-C55 ISP expects 20-bit settings, so we shift it to * the appropriate width */ context_.configuration.sensor.blackLevel = - camHelper_->blackLevel().value() << 4; + context_.camHelper->blackLevel().value() << 4; } } @@ -255,9 +252,9 @@ void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, /* Compute the analogue gain limits. */ const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = camHelper_->gain(v4l2Gain.min().get()); - float maxGain = camHelper_->gain(v4l2Gain.max().get()); - float defGain = camHelper_->gain(v4l2Gain.def().get()); + float minGain = context_.camHelper->gain(v4l2Gain.min().get()); + float maxGain = context_.camHelper->gain(v4l2Gain.max().get()); + float defGain = context_.camHelper->gain(v4l2Gain.def().get()); ctrlMap[&controls::AnalogueGain] = ControlInfo(minGain, maxGain, defGain); /* @@ -355,7 +352,7 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, frameContext.agc.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get(); frameContext.agc.sensorGain = - camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()); + context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()); ControlList metadata(controls::controls); From patchwork Mon Jul 6 08:01:23 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27203 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 C8981C330A for ; Mon, 6 Jul 2026 08:02:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 12D1766012; Mon, 6 Jul 2026 10:02:04 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="q52YsPOJ"; 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 F353366002 for ; Mon, 6 Jul 2026 10:01:58 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3F3CD1975; Mon, 6 Jul 2026 10:01:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324870; bh=26Pq/YMv4Q2kAycR9gxOB6lf/u+K9g7ABq4s1knJ3Pg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=q52YsPOJ3/Nz1i0+rAkgv1fAKaRdYopwuWt2QSVllAOu4FwPFqhs17bUGLUHn0Kwg lgSMssUeJg+cXLlJ/7F2qTrgv9qDSBrU+add6XB0+dW/1cidjySRPF3VNKBwUNArRO pC0mx+/bAHszvQMmkgbseTg2appgacAd5iHGMOvw= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:23 +0200 Subject: [PATCH v3 03/35] ipa: simple: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-3-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=13471; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=aSi/WEnYD7opU3254EPMuQWxA9XIdoulIVg/irDX+6U=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DvqJg9I5wThjKqQbzr4g1tCB3vmSZxq6DRQ IT81sGJVHmJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg7wAKCRByNAaPFqFW PD14D/4i2qzs6+YZE8/KcRBJaHgThobeKKpvN3/WnweRqw9Qhqdc02AmNO/Ku4gc36vn5NKnSVU JeaFzHyfO2eID4TAQz0YT5aZAFdwQG+pxMqDGqY9h4eGXoLRjShps/nGQl1UuuNKJ5lIo+LUt/i 3dWwc4oESIEjQYAhnOqb0v4G2MQ8Nbx1Wawq3RIiQUGGV7/h85fDyMmEM0U9RQwkUjGmIwOyLmB SmhS1gHm8adp/NNODrMM/MhlPGtM2KbqgzJPLQqK4rh83OLpBhBz8D18OWOeR9/8mHHcLXlSE1M x8jcZS7B6bvlaXJaeOoFr0wnXPi7ecBf6dcxXmq0bj3n4xem2Gfn4919JaubRwXL3aJ3b4IjiPv xuNJ+ZJLvrfiDjTrRBR1HTmKG/qBqw73tawPtGbej8HmHwQ7Cl3tFr4tvutW1qscRHvc7hX4SkQ 7Zype4vhGGvYrKoU5D8dUd9tjl7zucE6AW040gbDWV29LEXlsBCCPLrrMiloOswfWxcLOt90irp LhHoiny9C60Uni0iTnTDGWJ+kQZK0eF4S+Iic5Tlssdb02cFPjmhlkCz926V9ZsEbOWVnyP+KIY dA5/anRl/x2709I/1orwJ8pQkHEP3vhc/S2L9TVx/BpV/9uRhVxcSwPMI2Bcx9WkB6YiJXPJyKE 0oXD3Z31TnpHIpA== 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 --- .../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 Mon Jul 6 08:01:24 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27204 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 4E39CC3308 for ; Mon, 6 Jul 2026 08:02:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 689DF6600F; Mon, 6 Jul 2026 10:02:05 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="frvy343H"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9647565FF1 for ; Mon, 6 Jul 2026 10:01:59 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DF1AC1B9D; Mon, 6 Jul 2026 10:01:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324871; bh=6ScMKQJSBq7qeDItlcImfE7q4AwSHnLvxMUZRmbYI0o=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=frvy343H6p7b2Kpmy2fEVjexjcUdmjwXKG05FQFeW/C8x6eOmhfngQCv0mvb4eRuA OqbrfNiKDuhRGjqCr+58HxMV5YDZrVDYi4H3NasExQGTjK6PDey5R2oEP4rwb324XU fYXlyT/Me2/U/YgUzLroOgCTMBtP3dL/STs4vWJM= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:24 +0200 Subject: [PATCH v3 04/35] ipa: libipa: Add CcmAlgorithm to libipa MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-4-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=11964; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=6ScMKQJSBq7qeDItlcImfE7q4AwSHnLvxMUZRmbYI0o=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DvzP/jmW4SbfFef8nxBy5awCD6q5t6M9i9Q eWPtyFlcRKJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg7wAKCRByNAaPFqFW PO6sEACN837jgr2FZaI9wFJ5FMS7k2+nNZcghwT/5xodh4V64rM5PT6VxfhRp/mIikuD+2BGEaB Ccaa31+XQOQ8vTXEGEwPoBmAmFcgrx/S8TP7dqY3sNgtypwRq2iueoXMBES2c8Eon6OITIe/yXa gM0dRPj0gO6rfxVOLYcl1UlGEh4FGG4Gb0xYHBP9LJ/X6KiMMgjuGNfS2e/pYBBuwB4RTVhL0C/ xZJrMcEAdwtRXYXRU6/2MkuqRHBbgNv+PnApjtX9QdyVTWVB7oZwOwKcwL48TWIl+RKc45Tpfh3 e3mSVxaB9IvbCaQv/CaonA1ZAp47AGou8ePMXIXxTRVppLf566w7yjVyZKvj6WtaYa1y1Z75IWG DQgNJ9henUixcuHBPhYENaYu7uWYW7iQch9BQUD4GfSCWMoFl8+zwvDFJ/3wjYjxmMI8vP9Y974 CYZYwrKY5ST4/C3uJeWPhxiKX5QhBdiO3EQZwBdKzLTDNVV2V0RprS9eGblQF9H1s94P3/O1bn3 ZwZlG+iv3haQuguBIAC+kHHoXDY7Jvgk6dKmPhCXpyyLqRM0tfLydEAT0NgDeCchIAn3plwyIuD i+uBKVueo0NP1p9GqMLyp6UQ7BJjdHHvMJdXmuyv/FsXL0kd/iyBEy/a71AuF00UGjSitFItc++ 3PK+G8NxMgMKoZg== 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 --- 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..ce556303f4f4 --- /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 Mon Jul 6 08:01:25 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27205 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 66510C330A for ; Mon, 6 Jul 2026 08:02:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 57C3066023; Mon, 6 Jul 2026 10:02:06 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="sqxNi75A"; 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 5B84766005 for ; Mon, 6 Jul 2026 10:02:00 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DE8A11CD6; Mon, 6 Jul 2026 10:01:11 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324872; bh=OuAZ5xcMlcPP4/KppV4o/XVP7CsPoOZKhIMpI5r6T+4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=sqxNi75A7+i5gNI2j0Lf88OFcVB8s1+fg3CMN/n2c2pUcik4qtNYjomIhCnkQwhXw lGK2qA0MfpXTdvrniqahf1mG15D8+8tFjmkvvjnpcjiz6jQs6zn+FT45TBkDDgGwsO vKHuA4PHOqCjcHqNMWVWy01+GmgetXXEuml5M+Wo= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:25 +0200 Subject: [PATCH v3 05/35] ipa: rkisp1: ccm: Port to use CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-5-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=8053; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=OuAZ5xcMlcPP4/KppV4o/XVP7CsPoOZKhIMpI5r6T+4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DvCCHPl6OXdtK58VgyNspxFBVW/5kHMq1SU TuXRamUkLGJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg7wAKCRByNAaPFqFW PKvTD/0fPq9Wve8NGpWBCEX4VNabAHSinduK/S9fjLSfSxXQJjaAd0Cc3KCwaeHIQlSofIIGVEC GcGBh/iUbsxW9EqnWXSI9zsJUnWSKxjt4CymLEroC9KxxKPIUmcOy1hrMWwLxlsiq4fd27drGgH NOwVU2Rnp6WFqpqI0Ea8NCudwDZJs9jU541JXBcZSkdJtDMy84qAh9PpFichvNPMHl6kJCz+IAT wHo9QRNHCbs1e7KiY+qYBOAZvK/hep5PuFKTK/HrhqoWx/7JfeyhOX6FylTeRs4M//rRJzf8UwQ U/eyXxQxbgU4YjOTFXL/NovUJatdy3tXz/kYRtoZEaa8/VetTv8rFcw15HR41CGW7zud8Bp/9HA 6efFt11HvUFQQeTAOBMqsg82856WVG05EfjqBrhJiTZU/uqES2xXq19hOhZxHm57qzHtmg98Ttg idcaR3cyF6L1oHchCrSjJEA4Fd3y9+7R39Swv++UqSEBz3GQwbiZOXkTZDtc1J/f2hfjZp8wGPC ZTZxN3nugeHvw9x/UF3Oyog4UmG3MQhXt1yOdb1eHJp/tUnPebLAGxrPN83h2UwEV1j3IbgaCgw 35qnrvsDdPwSevwnE7jpS8xkhGdBxAmG5l+H4uC6e1GQfatq51rYUQ98dSajO41rWk5qMDaJ617 v5AFDZN0mbcTKkg== 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 6bb9c7bcfcb3..c1d58fdc43a2 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.temperatureK); - return 0; + return ccmAlgo_.configure(context.activeState.ccm, + context.activeState.awb.automatic.temperatureK); } 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.temperatureK; - 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.temperatureK); - 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 Mon Jul 6 08:01:26 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27206 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 6DE46C3308 for ; Mon, 6 Jul 2026 08:02:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9AB446601C; Mon, 6 Jul 2026 10:02:07 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QiaUVD6l"; 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 198C466003 for ; Mon, 6 Jul 2026 10:02:01 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 951131CDD; Mon, 6 Jul 2026 10:01:12 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324873; bh=0aoevUCURYI3RW4Kce9orfQpTQxMNWsWXAkdiMdYpCI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=QiaUVD6l9nv22/Kmpk4Fv+FSPHGeIWgFBXMkp2W2lU6T3e3l9HgW/T726DbnJAQBw O97l2Xi2hRXQg3tyTeezzB8ad4x2PeEvTPqknd4gASFeK4QVKll4c9DnHv37yBqXxa LP96s1PHyA3TLsPMVVPu4sL3mZcapHSqh9ywZlaY= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:26 +0200 Subject: [PATCH v3 06/35] ipa: simple: Use libipa CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-6-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=6092; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=pbEP19qQL6g2toy5rSUFN5IqndibJWrjdrrsGeT1tX4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DvY3TBg/Qd2OcnqmibNejWGO8b5FpidMTNp nmGnBv/wvyJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg7wAKCRByNAaPFqFW PGFuEACb2Md1wP6V2BOje6JBfM+9+f60vAu2hodRzDybrUSv2srVW7DyoifnAddTllcshPqAEwk Djk0M068NSS26Z/p7/NaLRziuS/5OSWIikzhT5QMVe/XLIceQpZfDJIhl3/2T4EymtnoJQXBQgp uW2CW8nL4s6Abj5fozjY7DBAxB21V7PzjcYW7cRAZ1V2FbofVowkGFl6RDtLxACdwPTQWh1aQ9R b7N3pG0FzeJvy4ROlLH4HUql1DtcvshrR8tGHvkrGgeXWCY7izc3FGNuuYWKsQwn/rwTyms2xD5 fKaJ2nNSzcOpNi6hCbo75FtrBouufTQXqsqbj14IaJE31IRF/+Yw2NdRPddcdeOA+Ux/vcl68hq tRrQNHzXEJvJ/XZAcJtjuwRRqKOpQSo2rVTLSFt7DEofIuVnVTyTJehRHhOTpZzD0pHpyMgdJX2 /shN5yHkVEOZSC71/fu8P6H+pHbH0v7nuYQJdhPGwQkYdttaNh9olkiYyC/ox7bGF+f2ON1AR19 k42/jww+EcYLFDVxJYccyw7wEbvc5H7lEPn0T2ax6jriBCEHYo3lXxOc8Hnm/3oBBYX1qmACpek 2VzzaHalXou3gGax9S0Zeh4tV9nAeYhix5MJXyQv7Jijo4ao56YLhyoo0fjbVzmA9vWGH8Cx7OL lVF4F204QCVDV8w== 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 --- 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 Mon Jul 6 08:01:27 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27207 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 27166C330A for ; Mon, 6 Jul 2026 08:02:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6EA6266026; Mon, 6 Jul 2026 10:02:08 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="tXxpHEdb"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 12AE36600B for ; Mon, 6 Jul 2026 10:02:02 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 79A241CE6; Mon, 6 Jul 2026 10:01:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324874; bh=4A9kajOFuKXSQXUsoFZ+WfGwjzpqM6yrrwEjlszpxEw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=tXxpHEdbcDvQZoF0QGXiz4kD1XjhzXBCoIvCXbaxiLDQDpLnmaUgEWUpGXnvQyLUI Imf2dnl5E1CHVv372sEkHHBVt1CdF4gAzE4YzJrhEqsuZ4TzW6BNXNOVJ92pWAPlnf TzBajn/p9Qq7PGM9wA/bsQYBDYXuhofIMGXn9e3o= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:27 +0200 Subject: [PATCH v3 07/35] include: linux: mali-c55: Update to support CCM and Gamma MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-7-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DvuVBOBYDxJXW5drmNHbeW95Ltti6eNZRVm 1xO4sGecKeJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg7wAKCRByNAaPFqFW PJPdD/963b6IT6SEywOWCFKiOIMujWpV9GhB7yNSEUzI4pGNUC2+6B6LnVIVBrtN6SOMudRIUva JmkVxYhcnJXb1LvJehbAsQO5nUMKuh7+pi62Rc81CRoy2khzw6NsATORaldGZsLhLgRRdU3NSK0 CcHsTHSgVw6BMCbHG5grMDhE6NuaOTLHAPtfaeuTPD85s1ZZzma4fqADme/P8lVczh5bJ7V62Qd iBPvfMFsLQrg/rb3Xz58siWfE3dBQXF/7QwqFxT/P/AdET8kAxb9sQoXAySvb6Odclr7x7D9IfV eSSWrFzqEkdrZEFrWlihnhg7KhsiL8pY8iqIWXlLDTkdFHqnHViQ0uMr6jlbhT5Dj0mjzH9G5Rg FmHfbBFs7a5o+mOJ05ZAPsfHRU+iwUgajbCX4tGLACT0acUQd6iorIHvNiuFWHGivAR2/8nAvj+ RVOfG55WJYJuMzS9ZrduX1G4iYyqXjGOyHEIAkXtofpD+w3D4HgGzeniYIyMkLnRi0v3dmZbshe CoSXNQLeOvfieHScH60jCQN2ZnK99QhF+OtCkL9zy8Ni2QgKcbn/yHBpaCUse+auNzW8M4JONwm qqOxjqlOeXbvU1gT2adgO7an7BpqspVeh/EMjnTqQNY0z9EdK5Of2KqrZlZs7Mi/Y6RqjLtZxd3 cLfnWOV9dEQ9tng== 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 Mon Jul 6 08:01:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27208 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 08D6CC330F for ; Mon, 6 Jul 2026 08:02:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7A51F66009; Mon, 6 Jul 2026 10:02:10 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jUF6NJNh"; 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 195BC66009 for ; Mon, 6 Jul 2026 10:02:03 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 64D3A1CE7; Mon, 6 Jul 2026 10:01:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324874; bh=db/6ss+/1SD4k80DwAdu1zmx2CfRPnAx4785zHjqiZc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=jUF6NJNhK41kwNXXj+3Nq52MCgeryAANhPmOmouHINbhnHUkcauMbK4FcPf5DhtXJ 9FwQO35oGz3cCxkQYxqb/dJurWUhv1BpVpoTIlqVtDuwN7RiDPWmeIuN4KQAyIrdhc d9BtWP38MrlM0MOE6dF4kXyF2uB4e+RQL9UC4vfU= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:28 +0200 Subject: [PATCH v3 08/35] ipa: mali-c55: Implement Ccm algorithm MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-8-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=9889; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=db/6ss+/1SD4k80DwAdu1zmx2CfRPnAx4785zHjqiZc=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dw1UZgv5+ptJ0Azofoyq35HgzTCcnoyreC9 WigNwDbrkuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8AAKCRByNAaPFqFW PEBEEADHH11N1DA7BXNSRv//tJhnPIS4NX86tZNutQ/+1Fwy6iY0N9LA6se+yr5GHF/1nyynxaM jGVDvC+GXVjd4wHCm0/m/kpnsawvXAzoBmtXSptjGD3JsQxM6h1veU1q6obm1mPgWU6Jz0osCVU UdQYSyglf8DXLnR988sbUqSPZzLpJ7tPTRfh4O1RR9qTQV0Nt95b2SpO7uET7mwmziW2XPy0WK9 H8tPVt9rY4dp1rUP9dcf2Dlg6xorgOAoAUSY/lcTxuDp+nM/QUHqxsvVzBr43d+Y+tj3ngTDFei djHH3d9wcTzEC2fEWCfFotsHvhAkH4mtC+duq/2Ukxfr1YdrY6fFcdFrCq9bYNaGegu2t6kk5AF SKjU6pC4vkhjdYJpzGTIURDI4QAxiKnbI8858gC1DKHTDEkyY+uahU74PQF87zPmCRmXrS1VJEZ qLxa4LlVsg77gemZnAB7X/k7oRrBl4ONNWwVVs74AjQoPIKQ46bj0P4B4QEPXsRf4hYCLPUosnQ RbE4fzMkZ5BUinyIjVz7nmZDaufkyZwDNbTdganOI3uR7suvJaMz3veyXoNnJL8Pl4w2H7T/qAB unGktpfk4IDTGwfggFnICFaJahdtlug7XBpfpui1SzQgpwwGTO+u1T9piIN72PjL/saICkSnMcP gBmXYt3QPwSjbiw== 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..68fbcedb2955 --- /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.temperatureK; + 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.temperatureK * 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 f6c6938b3cb3..aae4a543c1b7 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -16,6 +16,7 @@ #include #include "libipa/awb.h" +#include "libipa/ccm.h" #include "libipa/fixedpoint.h" namespace libcamera { @@ -57,6 +58,7 @@ struct IPAActiveState { } agc; ipa::awb::ActiveState awb; + ipa::ccm::ActiveState ccm; }; struct IPAFrameContext : public FrameContext { @@ -67,6 +69,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 Mon Jul 6 08:01:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27209 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 26C17C3308 for ; Mon, 6 Jul 2026 08:02:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A83C266035; Mon, 6 Jul 2026 10:02:12 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZWvb4mp3"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id AA58E65FF1 for ; Mon, 6 Jul 2026 10:02:03 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4F68A1D17; Mon, 6 Jul 2026 10:01:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324875; bh=+zh1/KZ1jgRGtmTQ5vykJHqeNYyq/krkRSwJSU9o9yo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ZWvb4mp3J8+RNckHT69WRODlW98QK4tAF8ORszFzQKeiyQyJ8z1MqSsfzHtysDFK3 q50PqouIh3BF0vTU/MArQSgrlmj/kSvsu0QtSQAyYyVllaV23kaZpTTci4/EJWl8qB RxsjZ7yFNKDw/yEcEIfOkFMKd0ksdaRkzzMhNt/4= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:29 +0200 Subject: [PATCH v3 09/35] ipa: rkisp1: lsc: Re-architecture Lsc support MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-9-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=+zh1/KZ1jgRGtmTQ5vykJHqeNYyq/krkRSwJSU9o9yo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DwZaTAE3zju3/ivFXXa0VqqbkOH/tJwDnDB XIh71deS/OJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8AAKCRByNAaPFqFW POLMD/9YNhNPRsuVh4LE09XXCUeuiQTgeMGA2sn0WnYwqftnFv5qfAQuq5mI25h75h3idwsoX90 utxJAyg66Wm5eWBLAEVqcG8NvmOzvj7gJDdeiPjgEm7iyzZpa051rcg/v2P0lRUzmAFrtm1O9t1 8JbWp5EuScR4ZpvK0BC8eEM0cX2wH9wyNf9S1LB1r96iSAhcqZ9RoM6jPXf8gtj1CZXTgCgogWi ZWFog+nfTcUtsNHzIYpUcQ2FMv0S1miuaDl+Oq1bVvOZXRPTMuhtQ8aLEBsUWcxsyQwG9zZE+K6 F8SPu2QrJ/FjwXYk5bR5srCfc/J1yDga7ZNcTENzqWCvG4vhCngKfTDiewC0WVtdnGFquULSS3Y WqpBEGUmduptOTdyAZVfgsQ+KSLg9vN2/gPsyW6FYFGRFrlBNb43eJTDPMxLsjodsmTThGOqN4a O70Y4sYhTTNF8OsXBK98ctWrKY0H8sNty40x7MCdgT8P3Rl2Nbo2ppnmdZCCUafBBoyrSIxqqWq gkDXOHVQvqRKXZMyTm5paaH167IvOxAXX2YNkUEc10sSTwz0A/EXzJGWW8iaIu3qxWXhJ2d9s10 wBKjd5nr5DLnGjQPe3/HOfQUXXaQpba0dTNPmxg7aIWEPUUuzzcemWsv0e55k5cy8C0EMNBl4uJ xqeMKUDNyru8R6w== 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 faf88effde01..92001319279e 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 Mon Jul 6 08:01:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27210 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 955B8C330A for ; Mon, 6 Jul 2026 08:02:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F12E966030; Mon, 6 Jul 2026 10:02:13 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qC44I5a7"; 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 2EDC366013 for ; Mon, 6 Jul 2026 10:02:04 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DABDF1E5A; Mon, 6 Jul 2026 10:01:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324876; bh=jrdxoAgoJgnLXUNO4CoaLct/yjp0+J0ih1FtMsDks9Q=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=qC44I5a78VZ0XNVmdNI/v4v7CIPk2WTNsTDt+HhbnkLIX23sCf7YZvwUkJY5Z783R vSpALxRu80BVe4YAeJxB3waSqla1h+yWVy1MbFWMKBrvjCibomorcH5/LGdNHiGNG9 cktnZ4VRclX5EGKdERJpNrjPp+qNge9CZovFLWS4= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:30 +0200 Subject: [PATCH v3 10/35] ipa: libipa: Introduce lsc_base.h MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-10-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=jrdxoAgoJgnLXUNO4CoaLct/yjp0+J0ih1FtMsDks9Q=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DwCKezc03cDU4xhKEch0WOul5t5Jec9nH+c pkyC3hvUdqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8AAKCRByNAaPFqFW PF86D/9sL1Qgu4cbR8QHfo9adWlQU4pCxzjqxVkUj8u49QKfDLpGnDyUrHMZs+LP8nggEJiOnfa TsOzazjNNzO3pVhmOTEoEkRNbqUmYoGqQk6ebIcjw7CzXw3s80naqVU0joggi9krb2MrW7L2bUt 4BSyvUSN34Qh5LoYDilEnfFkaJxRLDQiePg813Q82wM5wlBfgWD/+nytW1Yc4cB2vRBF2KZl+qb dZ3ylVu/L3OHjOL4U0H30X7CiQyKM+CSwEsyD5xsw/eo9kkZ41WNwmgJOKs21mtT1+D5dBWQH/X WuhONQgQeMXwquS2FFuqA8eq+KWPSa0jm6rgmGZSEHC6o6/OfsDulpFjjA6oIOh/u8MIQsozBJt YdPnR1nz7qKZMCmu95GXVDxnpZhvJH1/plL4Ek0GuFZFK54AdWOWnHmeRx/XqxgeM1OC2wHu3BS gvzt6vrDnKS8TqY+SdnqjopK5d+ynGlZnBqv4HN6Ki0EfM+9GvXHrTv8M3Hj422y+/+VrrmCtVp 2gHOhdHAJaHug2cVV6Tmuj72TdMC9gXkThvFMiN4LTfF0Z9dV3m2aCMagMAXWYY6NwrSGmeg3zv ngNZh5V6aaOu7xIXGE5hexqQocajZjq0atT1GHiESdcEWhRy5GT1I0wfc5RvfbTCmbCrawUZs4S Bp4Z2VOTVhvb8+g== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" 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..d545e9a5f7ed --- /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 92001319279e..e4d9f97ad009 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 Mon Jul 6 08:01:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27211 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 BA95EC330F for ; Mon, 6 Jul 2026 08:02:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 569F066034; Mon, 6 Jul 2026 10:02:15 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ger7K6Cw"; 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 D6C966601E for ; Mon, 6 Jul 2026 10:02:04 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 62678217B; Mon, 6 Jul 2026 10:01:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324876; bh=rYGOyJqlg6KGcH57Gl0nXxGuPBsSavnxemzwwosWMBY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ger7K6CwVDHOE0/wYDJr+4rad9DQcF7YK0MzaByS2du7Q9J2elJLG2zoi1VRXCTW4 3iUQuqxHliecUz2a6bFxs4g7/PWvMQQxvKNrawueWnjFNZdC1tY8g9vBwHaaY4KyOA qRfXU5HVCbTIJVf0KU3X1iKxUgxY372n4sb8qEl0= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:31 +0200 Subject: [PATCH v3 11/35] ipa: libipa: lsc_polynomial: Don't inline ValueNode accessor MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-11-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DwutBe6W0mIArx5GH3l9vgSqG/INojUX+be hlC8nfFp5SJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8AAKCRByNAaPFqFW POL6EAC0QzdIiqEzbmZYGhxVDGZZnbLby8mSrHJB2u7V8LgGUTcSpfI/lFpiXUPCOdavWJo+gcF oWRCko8MCc5XHrRrhVhgmZIgopYuAGFy4Fgkx5ytHIO/5n2hJeJ0Of3fx95LE79PYUSATdFGjMj B1zhh9Ce34x3Tqqg7mO4Q7E+qOmLhjEozGt2TB0hqqOXlMlIrSaAbggkvQ3+OK7hR9h6WwwsNqs PYjsQ5MSD+JHumVlF7HHEJRulT+ve/CDFzf+CY4BV1Qh1wWtwS9aU7wEFxn0CzwC0fbmHGocb6L PY/6WcazZfeGqYOjjsgyTXTCOyYwnqqI7+ukaCLwCaCqL1q/0WRlNib9s/PASp3lZY4LY8X+rMc FVomT5jnPt3DdOlWaunF9d89iEpCWUOHQDrYvE+quVUkl0D+HtkW9rhlbkQPc4C7SlZkokc9tHb mruh7z60rfPbG0EplEGb90bpDQh8NSiPtOwtTo5nXU4NgQqpKAxGrhUOdJ/LUltR6Uql1XiHYbA K8Q7jowmo862dMQ0ZsUqvFnxbEWxpK2RhyXesi1eMuu7GgWPUX6keFm7jXK0TzgiouE3/++9PWJ CRuylWVrgkDWkZOHei7mpQ/aRJ01dJm7xY16/0dZgSClRLo7rYKFN7hVarkI8X2ymT4gKN40LRd 9cnE6IMIxGfWYVA== 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 Mon Jul 6 08:01:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27212 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 6A6F0C3308 for ; Mon, 6 Jul 2026 08:02:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C94826603B; Mon, 6 Jul 2026 10:02:16 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="a2/NVI2w"; 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 A574966003 for ; Mon, 6 Jul 2026 10:02:05 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 16D9B226E; Mon, 6 Jul 2026 10:01:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324877; bh=R8CH/gKoNgq7+7xWYGNNm8lIza+GnSRJd4Fu7nOGcrQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=a2/NVI2wJcBQ2Zwv3R4Mje/3NAd5kCQws68SGLt7w8fcEgmNCNAvMCbWExWr7NcSJ XJV++8esQRIBw8UhYmreOjncllSLOWe2IVBeNCJ8nI8Iu0FuK5scz+QtVUJCg+fa1p O2+zTYWnUtItRcNcslnRJXOCCL8LiBSdJJvqzpKY= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:32 +0200 Subject: [PATCH v3 12/35] ipa: libipa: lsc_polynomial: Rename LscPolynomial MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-12-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=6230; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=R8CH/gKoNgq7+7xWYGNNm8lIza+GnSRJd4Fu7nOGcrQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DwPBCfoqMgNdoUqYJ3Q1IcsisuQ8IqJPbbM hbvqSkVSnuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8AAKCRByNAaPFqFW PKekD/wNCer7StQSmDpoqU5zWCbgdnOEDjBwLEwL5f//ZmgoUT3t5T1tf8WZVlpmctJPT4f+AY7 Xvwhj/rBLYPPhrv+DS3l4YXV/K0SKEfwSUMHUY1s7DfOJV6EBVAtEzpXTwi3pAH0jMVrOaFh8Nv BbhWJzywvtky3KOqH1hDvsCkAJtdCNREq2ISGVaXq6iwjzKJMk5TFw7SToTkV37tmuxyjZUoWNu BCA5gH7uygl5l9NXfs6Gfp7OjoahLZmQyDeT63a+2FrpdVPy1x1k+4BX6G4ujkljogxEf2lE5F5 v0vspkt0gr+TnClROdKX3tQlpPiz9ime8orgXxyNp40OlkWD8LXO5vxfNdXiDqjWXZHIu/Swa0I Zz89y98EBmY1vIo4gQLx4JjU/jEwh3xVQNl0UKjqmUPtNjkuhK8BO7g5EOwNlmylTJyqxrYo8q6 NKrXIXXA4RdlBRuVeH4Hk/DZtccPL8+Falrr76wn4r4ogKi5XacxvhWGemv5xn11qWhzOmuxxA4 uBegl/RYmXp19FOoMoXAvpVVO/0HTSs4yQDo+MNlbLXuNEOSQ/MyQ4hHxAa4cwyLDnKqlUyh7Xd bdfQYQxfg+2MkZmBmOXTeiCFJzHmly0ygGM7TL5cWcKBfjEU/+C1CcHhorpg4n5wbjDOOzEuz3x fjGsTMBgKhVcQGA== 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 --- 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 e4d9f97ad009..6c6dce08704c 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 Mon Jul 6 08:01:33 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27213 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 CD571C330A for ; Mon, 6 Jul 2026 08:02:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1952F66041; Mon, 6 Jul 2026 10:02:18 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="aoQorZNs"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 97C7B66024 for ; Mon, 6 Jul 2026 10:02:06 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4DB56293E; Mon, 6 Jul 2026 10:01:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324878; bh=oHPPuVkGX5pi4HusVH9TgGsRpDNtBgpyaSHuyM2mitM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=aoQorZNs3JTIXciJ96t51PtIVWQcE7yFM1zuRWvNPMneNIBMQwKqO1QI2+/RN+ECC qjuGhT3dcqDhaXCC77f6GpC8qS18Qt91qoppcChWujawjpsWr4NmgeDxR1GxVJhWAN HBexJT1nKwajE0hQF/EUH/JhwMnDVzw0pNycfpyA= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:33 +0200 Subject: [PATCH v3 13/35] ipa: libipa: lsc_polynomial: Do not inline functions MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-13-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=3967; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=oHPPuVkGX5pi4HusVH9TgGsRpDNtBgpyaSHuyM2mitM=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dwvr/mU9Qeis5xYR9p6ugc+3XnAT0mB+qDy EKDdJ6SwdGJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8AAKCRByNAaPFqFW PGRJD/9EgTFOVj6S0LWr6BkFIOkXf9JUOeKT2PHLNtxA39ACufG6eijIaUxuAFGXQC6NfzOPUrY XiLHtMj5JDltS7/zK5qTy/Z1WYno956UpJu8xKQKUjXHoq+TFPHu+wnfpWMIoOiBqtYWfG1nnsN U+htL0iUK5b3UAif5TRALKC32F1pFyUHeCzBDvFoNN0g0y1auwjHs4WQW36VFexwzUeQkthlU70 Q7wyCvDGChiuGdciNupGLwVRv7ktwBOxGmoMlXD64+CVNh+sJnONpFOJjewziJdUnJOsJPwfSfG qyySsa8RpKajhYwgW4SmFZeordqJLOVrrdSe0hwTeptI0c0StxVAOi6TgsHe1i3L8mabkIx+Ixa y9eXkBDAizlec+IyZaws+H3S6YEnYCbhDaYngE3BOdFq8DFl1mfVPo4TVdOT7/gdYcgrNqkcoem qiaR//Tt5t4hzLHO8VB5rHxxEBhr5Ns6DsTUTCPoyj5ClRqLCwRZY5PEikw0DTcHwRCjQW14Tgq b5oKrq9wBzzdhZf0alScCShHLisnwmUAIvhwh1eyUEGC+Jy4RgKyUELLN98UWzGoFK9pQ8XEB+8 Jm8wJsJC95ceMM+f8iTcMl8zmtmWiN1XvcQiHGYHBj4S3Y85sJeK/kFxOMcbF6MPDCBSDvzSXEj YNN3Pn4iORP0ujw== 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 --- 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 Mon Jul 6 08:01:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27214 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 ED594C3308 for ; Mon, 6 Jul 2026 08:02:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 92A2F66029; Mon, 6 Jul 2026 10:02:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Ph6LJNb0"; 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 1AC4065EFF for ; Mon, 6 Jul 2026 10:02:07 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C46363DCB; Mon, 6 Jul 2026 10:01:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324879; bh=k8xx+n2HYzN5DqXOt0UsO1jSOSRlbXvar8LoCiAJnEk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Ph6LJNb0OLjfuy8BNNyfEFqQPaqNhEVwaWlIBzGnVvE/xb6oeBjGQrBxrCRK7Itly jACeDqbTHTk9CGkGFttR7jmbiFYr+W2lqHKYpdbVbcK3RM9s1b2rH0c2elfMhbOzvb 84rN3ovYmXjAIzGUq8x98t1FdvfzuNUqw6mi8XiE= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:34 +0200 Subject: [PATCH v3 14/35] ipa: libipa: lsc: Move Interpolator to lsc_base MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-14-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=3266; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=k8xx+n2HYzN5DqXOt0UsO1jSOSRlbXvar8LoCiAJnEk=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dw9U/pmS4lu3hPkbq1LnMtHOKbRUeEghzFB yqAalZr/RqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8AAKCRByNAaPFqFW PMu9D/9dAsTes+GRqDtHvk/EUJuz5hBig2ElM0nTcpggiH040dSm3RKvUBBzZfOzYgLeS0pUdlw EmLfhMADb7bgNq9x5kDU+6zjCYZzRWU0aS54j8sHYA7O7YBJdt1mfZZ7qnpirQnqRpzPiQlYjTe g7B3pnb+UpxQQ0J01c2hKh/FRnJeo7ts5wJWvGZpCUThe7zn21Z4dIRWTvM2tiM2Y5UTrF5TGUU aQ1q32X1mm3YisTI4Zt4kamGMaP38XWhtOm2vX5V69AbrBvyu5KrZmeoKySg/mIpfV2RRtM3Sgh JflR/kfkDESpEZtee1skRFlg4yse0Cwn4Red8RQtVJYucBLnr5y95n4N3CTX4OpL/KMB9nQ8R4m nztFYJlp20VJQE77eH5eFoSLlJmrOqQl1X+jIeyWkfwWwR9p429DtRtxEX7bRqbr18s9Cw8x+Zo 2AAoeZR+WJaJLkJ0CsUq9q9Ygmb7cpO5zQIcmOnno64v4/Bz/QfANqAvlFk1zFSVznke4PegRlj cUYaAK3p2WVmTfnRGQMJN6Esqmee67FjyNkW66XARz2dxS3BWb48aSRzZSM8VXOWQk4msxgeA0u mZwyHC0M991qhks2Ok46dt7FipxGwOMBmIpFlr21kNtk0+iIRm0ba8nGNJn5kzCp1wmC5RbYJSB NOt0+IArx5UAYKQ== 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 --- 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 d545e9a5f7ed..bddf131b85e2 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 6c6dce08704c..fec698234583 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 Mon Jul 6 08:01:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27215 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 17FEEC330A for ; Mon, 6 Jul 2026 08:02:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B60F866012; Mon, 6 Jul 2026 10:02:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nETQlIne"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 971F56601A for ; Mon, 6 Jul 2026 10:02:07 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 634983EB5; Mon, 6 Jul 2026 10:01:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324879; bh=7mKR/PcqY5yncQn+l3khU4vmC3Z1hJLyW/AG7qzvP4k=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=nETQlIneLQKVE/AaLA916sbxo7t1s7Ow2Br5UjzXmzLS6buWtVTOFi8kpIcOuBvEU n/lcqdvTSlV2geOKpiTGtZ71RAoDsXSGr5G8VQpoaWW7x5DCfvAbg7hhnEU9BRUuWc 9ZicQVgRqGjvoIoUYdqdGNAL0lfFKzQDfHNi0558= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:35 +0200 Subject: [PATCH v3 15/35] ipa: rkisp1: lsc: Remove rogue empty line MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-15-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=538; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=7mKR/PcqY5yncQn+l3khU4vmC3Z1hJLyW/AG7qzvP4k=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dwm0k/6aj9XMl2qbQO0G8CX4QwoSzVWV3rd vQ3hgJe0SuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8AAKCRByNAaPFqFW PIGbEACOeJMfJ5jbM8N8Ds5NnyFcienoLsm074O0OjEwNgv2r8+34v+YzSNBgfbHYBbW3BOYZfq vP1zTr/B4+uSQdzuTUO7yE2pQ862iE1uVGAHji7hiGsAMS+6yMGEWkSeRR/clCGljfoHDxz6Ur1 y0Y0nt1OuLwZA5+L3M89i/4bphNQbHHgfU63eRNyWOoCHK6SUcmJ/lrwlLSvZjJnL3rnpOL0elX AmyAUoP4V41Qwckj6LRldeYM5gyoPgS4EyNWjDqabs4H8+4HbSB4nPH5qFjoDqt+yS88OQrfduc TNil2H/+Rtdfv1OdjUS+PKfzXxFKmFYMTiteugsnp1mGLBmiI9V0JPo3C/RoCen3MEtPfrbjrc3 ineamHjordYTp4EsB9qXl3I0VmPHand3V9cJJeJ0BIZ2S275b9MSIx7J9jRnueMG06kGwDSUIlK 26RfOCvAHFGmOmR76D/ltK4nF8gvqOKRwTUj9amPn9MkFdw/ii74km2EfRq5c+Joz9MBcZW+7w8 5kCD75/SS3h5eGxea8Uq8RyF9huQal/X1OVc8AMDY0WTY2Z4O9wNfD+wClXJD/yl5I8GfCeLyMl ehyaSfJ1huCJYgiSfSzIeCXZCjzSkNkzmBKkJdaWnUxHBoCa8uA2HSALzaQ7SClBmdHDoVJXuSg MK2CLBMPVJ+di7A== 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 --- 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 fec698234583..910b2ced292e 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 Mon Jul 6 08:01:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27216 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 8F812C3308 for ; Mon, 6 Jul 2026 08:02:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3617466030; Mon, 6 Jul 2026 10:02:22 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AEt4VwSL"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2B1C76601F for ; Mon, 6 Jul 2026 10:02:08 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D4DD63F8B; Mon, 6 Jul 2026 10:01:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324880; bh=G6B9NHf9Mbk6aw4/wLFkZw2N4cVuZsUD7Jc9FKcDTqo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=AEt4VwSLCIhpU+1NzvWlMcw+da6XnJ+uEJ7hRNcueELJKUMaY/7q8BiHVwCwnbq7b QD0PM/yx8TLqOkmu0LHvITXAMP6j8e+egoBaKhQV+E7uqR8gZYZojRP7VbP7a461LO 7we/zKtga+ea+ZHLjrPmV/l3XYzZS1LamXSbzmeE= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:36 +0200 Subject: [PATCH v3 16/35] ipa: libipa: lsc_polynomial: Move LscPolynomial from RkISP1 MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-16-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=14417; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=G6B9NHf9Mbk6aw4/wLFkZw2N4cVuZsUD7Jc9FKcDTqo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DxROsbwisYvoi0ZwCPFeDWnJQJrq9Nq8sHk /N5Xqr2X9SJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8QAKCRByNAaPFqFW PJaAD/9ODXTU+5orMSCr1R0LnNTUVUqmUCifTP9PNGmPDu6EpZuv3iHicR8F7apFxm5p96zjHi+ PJdq+lEQXK3MOLeLhupaPcrLwsZ1Ech7MgOYUQ/qzwglNoMdffiL0pVPuAxHGS3E2nmVVPDKKar xJl7+m2TOLSaYsVS7r8UQEd9jwkd6nBNZLBvtEq9hS4MSLfZ7TnRWGUXoTTNZkOedFGJhN8lVme QNfwZHrdUctfMdu3rpU5Nzz8wwgGN2aHtHxyfoT92JUyrPWmYfT7pFKNI/HVbDAOMw0Jt+Vx8iM OKspJjDG5tTzWKZYrE9FdUxBeTC8+Zv7icTavfHNvNpAIyWvLvxmLwELz333sJtOWpBaV0QMAE9 9QU7G8WPzHjeFQBesbGOAqTtkst//May3YnVXdly4S7ufvAJUT5HBTFKKwpitPILQzl37jTrZoV H457lOvFfahfY+RCDqGcUab+3p8C7mP6mBDYqjSJGdMZDRVORmLF/oGCWv0N1QNUarnjLMgaaIL +RxcJy1yjdyAEdP05SCBh84LF/KmwxXTHfUPJNj/xMXZeSIa993xOYrNUuq1Tuh2j8WcEcmSAL6 xUHkqO7GnbRK0rXOpf0Wcstal397CAtQ09R8sdvGCimhifpzkbDh4zlELKJKUwZ7WWW8sFJtB+n +tw4hZvLwqigtXw== 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 documntation has been added. Signed-off-by: Jacopo Mondi --- 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..5c4ffc98797e 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 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 910b2ced292e..a4ea879aaa83 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 Mon Jul 6 08:01:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27217 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 D4D05C330A for ; Mon, 6 Jul 2026 08:02:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 85A746603E; Mon, 6 Jul 2026 10:02:23 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="riEF/JLG"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BE1F466012 for ; Mon, 6 Jul 2026 10:02:08 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 63C043F97; Mon, 6 Jul 2026 10:01:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324880; bh=88oJ1AACTeEN2ePgzO63p+RFA3svmWEVA3UY8uvI+uE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=riEF/JLGUHcEuT10KM500qiTYpd4mJb0ct4SAa4mRgNp8i7fV8ZVypJydeCEyRnUY SkiCTfUkVvxmcsjYENXxvy3OrmkxbDNuRHpsS6TfujICkkV/wLQoK5FsDVihS9aCnQ N4uC3ATSa1QZ4Eb+MYN1xcMCyDbvPrYp/C2WI11I= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:37 +0200 Subject: [PATCH v3 17/35] ipa: libipa: lsc_table: Move LscTable from RkISP1 MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-17-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=8250; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=88oJ1AACTeEN2ePgzO63p+RFA3svmWEVA3UY8uvI+uE=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dxz4aDjjTOqGnVVAdtXPyErqIPmDZISmhIl OGZYsSEVaGJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8QAKCRByNAaPFqFW PH4rEACPjuoN8AMDCzCUdgz6DSenhb7b3YMmPQgWhkPshwQQsK67w2qcD9ydfOkhF14txNlU6fv hzysI9tCXyAIlBFpMARA/MSawmlsjPaE3Msa1QYPO5VJazD1DtD5c06/D+MmzQ7QyKR2aHEwuHg XylmXbXn4hWvOSM5kJEC9UgbNJ3Xmqd709D+6RHMDQe9ONkKHca6VO8x380rbicdZuJKwILMuGU GBmMrNtcnIEuTIT7A7/JVCoK1O2fSP/w4trZKDDuUWIeUh7U+VB41awqoUI9BM1dZ5QxgYe0uIN dFipS0fCMempmavox4dURJSbj8eiwwOEOyYKcY8OojZPz5if8ja6G7dT5YEkdLs7xxQEzB/lJxm LRGaUHx2nO/SIvZH4G41mLkfYYg96YOLZtBPTuiVhbpM0TTu+ZOFIcfiqT4j7CeKKJnuXdoD/Uc q09VkjSbEzKcsfaWfWC2Fsv2BvibuoNzMNSQgnV3+OBSr7Ug0L3kFE9RTocMg6Z44A+qVP1Kw9v 3qen15REbGZFuk+otpnVPrHhLFkdjvsNQPO/FKgWbQoQfNpAS2bx19NJfLZZXYBNv5IDtV+M+Gd uaffXlHJfrbq3oenm09wv4Z0Z52k+jkY/rmIZG7sRQH0i233CRCC4v0k9G5zlnCfMe5N6RMVepN nb1N+XzpG4lpOkg== 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 --- 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..41a862b92e88 --- /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 a4ea879aaa83..974534175e06 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 Mon Jul 6 08:01:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27218 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 EC0E2C3308 for ; Mon, 6 Jul 2026 08:02:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6BB8866050; Mon, 6 Jul 2026 10:02:24 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WC6IXRhI"; 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 49A3866020 for ; Mon, 6 Jul 2026 10:02:09 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 04E4E3FB2; Mon, 6 Jul 2026 10:01:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324881; bh=Xw4RkjyI4aHlrd1nRNO7E5mB7e/vwariTzp8M0iWH74=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=WC6IXRhIxStTzidNWmARvet7bYiDa7ZGP+lCoIuTfhQWXLjk4IVbGb6uObke7hUeI rQC70ILkPQxmuLsD0xdgbFZSKTZqr/iIi8DstVgjDZISdu8GEv5YEo8tqz1H0EGO1i sSAuf+5fRjxAkvXVyxN1oDaUJIv4YAdHoacn97jE= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:38 +0200 Subject: [PATCH v3 18/35] ipa: libipa: lsc_base: Add sampleForCrop documentation MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-18-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=1444; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=Xw4RkjyI4aHlrd1nRNO7E5mB7e/vwariTzp8M0iWH74=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DxKro/8/JRvI4o0UshwHU/rSjaES0TMev6X PJ+6amKxM6JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8QAKCRByNAaPFqFW PGzcEACZZhY8Wq07MAAzpMQ3Iv0E9rJIlBa1rIBPf0130Ob7EAvPaSR52ZyW21TOef9wQMDFTH4 cqAVpTeSwZQfGLAY9xv5MXpB/BXKpWXOSyLGZeonEX1Coodd74vzNc5qaGcZFD2m8IEss0t0Hgz 2lgAL8h51FvuUQx82XsQOo4wPubFwc4YglGnfTqihYufoYGx/c0jksambfBSA2Ci8+rGMZoDZPc KqGgA0XzCTtfNdgD0qxhHTiin2vNUaOcbW3DzNfWIPjpwU2aTzAmA4TJsFAGoZLu8j3MxhOV9cW 4PDakIGGy5qe/dtyBACAZIxANxaGKQqGcliKag6ZrQtcnCs8kqAbHGiG9UjqvodAB+062ZYXind wXNfUGDBrzLae3MkOdmG0UbDgK5nvjHRqwQYVgF+r5751/xp5yakD3beKnk20WjuyxsfPkD/3sH jNZb+0pEQqIqYaAsWzWt0vDvzcbq1lQVlAK+vATIxy63NSUhYY+focX/prLeM7tHISKywThV5lC yaW8njyd/+6MsGwIhRpNJJkPG61jCM5GJ/tANEIBt3GFbFRGv3DJ6KpLD1dofar76ZDq32Pe33G fua5NiL+0kAOWb4k/dysG6bBa2vAdHfUn1zWul77o9XQJn9v3F48mFuhz0mFlbtpUu0GWIbl+FM jXvOxkr4T3ikwsQ== 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 bddf131b85e2..b055249c066a 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 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 Mon Jul 6 08:01:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27219 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 10B9DC330F for ; Mon, 6 Jul 2026 08:02:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C410C66046; Mon, 6 Jul 2026 10:02:25 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KcbHoquo"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DE94F6600D for ; Mon, 6 Jul 2026 10:02:09 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8F1F53FB7; Mon, 6 Jul 2026 10:01:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324881; bh=8GlTgDyPd4f0znliQ0xwhTjrc5pCxzBor2pnK/OPgzY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=KcbHoquoeGq2yEUVlcbqxBWA/FutsjHiFCxXwwOTxJ24cL/BXNJe2Iudcswzu/brU FhdBLMZPNU2rpkGD+5a9mpYMj063gBjEW+vhei40k4T9Sr7W4Kh9aV6n081x/XKZHq TQpij4UnMjel/0xmEVsySVViZO0ROTRjw8V2U9uo= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:39 +0200 Subject: [PATCH v3 19/35] ipa: rksip1: lsc: Make parseSizes() a class function MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-19-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=3484; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=8GlTgDyPd4f0znliQ0xwhTjrc5pCxzBor2pnK/OPgzY=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DxUR/+gGv6EvV5PtMpO9Ku6pOvk0a5tDprd WWui36wMFqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8QAKCRByNAaPFqFW PCnkEADCFrSNiVuCrWEyLGdzHdnkyPgvArRyyEw67VGz8tPFxnQIWVklI+R7QOsMR+rlMi9cJY5 popIlO8Vgf/EBsojLwFcDl39tM+MHj3lBjL7tSVQLrKw08g/hgDaFtfvdS6NcdZvv23rRgDlnq9 e7eRqp1Hv2fa5S/yCPsGnfjaxHnjz90dOKs6h7gDRyJAEMXXY7BRPXqtqy/j0TeALXW6FsqQmHh uACLrqBfI5IcTxjRUk9Qjm9te8BWCyACBRHP8Kne4yOESACTb2YW8wQuqQdaK1Pydea89IgRfbD uFJBJeTWICCo1w+O2Nc5D9oJZREqHwVeu+mTIJcGRQ4LbA+FpN6QtMEI4p5IXvn4btejSPx0sju EIm+26EDMWzSEW3Qt/wwDaE4Tn9QQNFWvfUJO4VhhwLp9c9fkMUuZmlgijaClZfZR7DB114MqSk YjVS5giEo0cCc8DJuTyNZnyxxaUUarNNWzRiOMsHYNt7SRyc8Ga9v2DiX7Uv0OU/M8rKPY7uFeC 5Klf7fHs4NabdOuW7mShKK84Ivb8bh6kRLcYwM/DbYn8VgIQbtKjS6JCOpdOVLmSvF9ymGXnWH1 hwyE7PtQRgLoknRyNKXlnmE4gsKbX9sYUjkF9v6Dz7S0tbjG1ryM3bfmVhXFXfkgg+gXB4K/y4y ceMskWh77X1Ohng== 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 --- 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 974534175e06..0312ce2f8bb1 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 Mon Jul 6 08:01:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27220 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 BFB90C3308 for ; Mon, 6 Jul 2026 08:02:27 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 453486605A; Mon, 6 Jul 2026 10:02:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="D5ZIArOk"; 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 6013F6600F for ; Mon, 6 Jul 2026 10:02:10 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 250943FBB; Mon, 6 Jul 2026 10:01:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324882; bh=mHKfF1RDGGy35TUivh9ViYo0RGbHxlxDi1lphRHZEQQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=D5ZIArOkRiGkERcILuoDSaTIeJv4pTq4yOflFOZNpGn0D2A7m+Kn4TUhDfXedDdI0 NYRr0rEBuRzoXXYLKN9EVsInGV6y6FAx27nTrHio6XeOHEKVNltumjoZOuKqsfBnn0 PY+vh3yLeAw5k84OEGXZ+JIDJ0LSKs1wg0VfSojw= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:40 +0200 Subject: [PATCH v3 20/35] ipa: libipa: lsc: Introduce lsc::ActiveState MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-20-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=mHKfF1RDGGy35TUivh9ViYo0RGbHxlxDi1lphRHZEQQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DxI6hmk1QzfLjzfi7m6hHRDgvtW0OVmPO2F kY/5oxHMlqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8QAKCRByNAaPFqFW PCEDEAChVoFaopG810bU56/rRGwBFWjdZyaysOLVMACfdk6QVgJFp7ZJch7u7rQ/04big4dOoLi XtASqtY2eLGonEnrh/OpQ6HvDRYWzuGXK9YEVFKY5GYp8hdoMgkyMm4B53VaXvcHoKjAXaQsyWv OQToeKv5N8/eHX37x2O2qCdBJ5u8BOiBCphbkbJOZKciEbB2suKsaPMBoAH08viYooS4zqy4Bs9 xNSUN/TCPsh+mUgtnnv95SqrfJoQXIfPUQJxh+Y9cfZqGblNNFCO+iSE+oWKJ7PZB0FkkdX5O7O yhJOGE9Z0+tsZatvutYCoK82zd4ZRPqoKD1Oom5XTY6ZFuscd0lYi1jKwkLv3+cqP36jWiPh7Tk /eu8UcjWsZQlPM8k6rXKVH3QpamYa1EutTkWaoNZ9my7s/yZ2v1cJ8BQdBI4frqUIRX2xKDQ8dc CCXkVbIv7DgGttz9hEhhdvNQghbUtaM8MmD3oi7tCwF7PwGao2kuaxPM3WnHpysjWq9tipRbpE8 aickgSjMJJmvC0F3oasls+tZ/1SrWdJ3VNisV4I4yvhLw9SgvSKl44MCYd4T5xxDFoD3x5sd3hk LiKPiwK9gPW73LEigl9vlOMWyK1fy+zcCIX5uHzQZLXMSLBe7eS6OXZ/d95Ea2BqBWpSRdKuJqs u9QR3ZpfB4mFAnw== 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..9836f27162ba --- /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 Mon Jul 6 08:01:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27221 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 C06B6C330A for ; Mon, 6 Jul 2026 08:02:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5BD8166049; Mon, 6 Jul 2026 10:02:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TKsp1mw8"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 315556602E for ; Mon, 6 Jul 2026 10:02:11 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A29693FBE; Mon, 6 Jul 2026 10:01:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324883; bh=YJ2FPMp0hVuB4YOt68Cb4gpvZKXzqK1ZtaZd0CDATvw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=TKsp1mw8wPWUuSrZxXB7ASzRRmb/N7RaeOWkbx0l4p0VjImmFwVQdgH4MO/rgwLby zH6IrQcLRk9QrnmreRNI+1SnzXT5Rj70zK9A0NsbtGU5yaYj/sLDZRHtiTgTxLbN4R 6NYcZAMmtp2ladQvxggi6e3TImpUs3IRWxVZiuEc= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:41 +0200 Subject: [PATCH v3 21/35] ipa: libipa: lsc: Introduce lsc::FrameContext MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-21-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=YJ2FPMp0hVuB4YOt68Cb4gpvZKXzqK1ZtaZd0CDATvw=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DxaxV357qb82Fe2/sMOqjTMKdwWGxwd0rKj kBDhplKvHWJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8QAKCRByNAaPFqFW PGZUEAC9Tr4rGmBp30Eex/i+IvtcwZFnX1kqlVbWlbTNzwIh7y6fwsiPqNn8/ULxbUVFgQMDI9C Zp101DEYQcYlJa54+Ciyb83XhounvBoUU0No0JeS/iTfBvQtdc0ZwXGeoQPXKBF2xPaOIli+wSX SBKAem+Jkq8G0DLEsyhEs0CXzHs7ybBPr9uCLGqwP2qGujqS6VP7TFeGQbIFIE5xuzsG1mJ5WID k9KnSoxvEtyS06EvUN4b6rb6fVdrGZMzC1axnQmer/Ty9pjv1fBQ0FNX6X9FzRMQ+MeK/TD2+Vq 9jtNgOwBNWAu9reZNMJFW5Fo16Sers8kCu0mLqE/Qi+/j+A0kZkHAHLzvTA6M33ahaRzECUC6Ml mzuApW5ZA5u9RrrxTdQFtOmlMr2JGkbSVRSzH8S8+KzivWqRpBJ0hVawisDvqUAtmOF2rg79ZHG X8iUGXKwUDYYTmAR8joS2tHq7I+8xFY+YKKMj7YOSrTCmx1G67kYh0AWHvv/V0SNoUco0AChr2R 9aFuzS8AzIH96yrlk60ItX5Cq6TGRA+NzVtt/inWpfcnYVBkY/w7RQ3+DbTvHHVs9qDoweQUbtA m5TWypoUzB9R94xcz7LeDYMG3BLJ3djJs6ddCGp5bBAOx+77Znqnx+Wts5iT7l0J4MmKd3AQINq ijfLZCEkTjJ4ADw== 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 9836f27162ba..b789ae98529f 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 Mon Jul 6 08:01:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27222 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 B3D3DC3308 for ; Mon, 6 Jul 2026 08:02:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 52E5E66047; Mon, 6 Jul 2026 10:02:29 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cWo2d4pE"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id ACB386601C for ; Mon, 6 Jul 2026 10:02:11 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 663161CE7; Mon, 6 Jul 2026 10:01:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324883; bh=e7UIzUv4LL94mHhFMVLNQ03e/6HSMMA854bYuJ+XUL0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=cWo2d4pENCpkqe7WLwvZooJ8Ca6WS+ApofcsN1eYPAMtuhwGQcrj0XvtzDPK4JGd0 90SbtHt7fgl7AS0iSTN2NPtMyFO71oxInC8Bb+s8Fxwjyk4V1JjQSeCbU7WjTsrAMe Feo2qMpW/H2/E+WRIaznCy7wa4sorTiGDbytR/I0= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:42 +0200 Subject: [PATCH v3 22/35] ipa: libipa: lsc: Introduce LscAlgorithm MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-22-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=e7UIzUv4LL94mHhFMVLNQ03e/6HSMMA854bYuJ+XUL0=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DxG8GCyITVP3LBLK9tJmZllOardmn52ZDcY +52EC2FGy2JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8QAKCRByNAaPFqFW PL+2D/4tdPVUgsGA/JhkO4Q/LzPZo45nCKBAweVHNtYmf7uf5rx2eMTe3Mq5Q2lvD6UPk9k0VRV FZpTOs+yXd6kStx+lJkGvfxfD4va5uoi9IVhk82lvobMmldRIJ6XMJ/+z3dZLVEVfvmmYpo8T5U +jBr9ut/YrRTzwC91Pg7FQ97mAztu4paBcUDWubF5+sd8CkWOreN640fBGkYtDTIMDLaZZhdoGR gFCVrKeOtD/jSMJk/qypLH6u+/GuoUitTpa4eQiof+4UlD1jB3cljX5oNQ2NZMiDsSkwh1q4ndt LZXf/SqjQSkH6lhjpmSuspgM13KR0W80IJfDevETKvrNaK0uVcC94E93FXahw81wXL888OKO3wq GFKo3CuIba1X14LV2pZy+Q5dDxHTo7LA/cehNi+D1JqmEWseW2kK3pZ3OaZLCYe79V4MOdXAgyX dEotZqRZE7ZlBjSUt82kOC3FeO3VFkcxw9mfSN/j6FW7MED+VWH03P1FIx1MKGTM17/U6lyJtf1 dpKZ/H1aMJ1fhGOyiiJPsC7epTvjUYhrcYZ/I6o1TGCRGte8tqnGXfiZlRckOdAhSCadOxr0XXI Z5+PUFQ49Aem/6+Lbs47NBDjp5FtLrMXENEET9EY//C59BK118iDB4xiryA3FmImFd/F0w5IvWV MrVNOWmfykrLSCw== 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 b789ae98529f..66d33a2dd796 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 Mon Jul 6 08:01:43 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27223 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 54070C330A for ; Mon, 6 Jul 2026 08:02:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C6AA566063; Mon, 6 Jul 2026 10:02:30 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ovdmCUrc"; 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 7F6D56602D for ; Mon, 6 Jul 2026 10:02:12 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EA2D73FC2; Mon, 6 Jul 2026 10:01:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324884; bh=gU7UoVQjDoxjMvS0BE4xv1ojYvnjxU9I7ZQIqWCiBUY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ovdmCUrcccbkaaf2ZfMEPE7mbK3Ah4smWggu44dJHo5coa/tQJKQsuAoEgfS+I+Dw IJn+VB9TfJr/IB+MKRpqAnNrtpd3nkRZJXyfv62acujnKkKKjP+CMSa752MKhZWeSF 6XA5tX3tky7i1m3MrtMFBRZSVJzJ51Crj5KKBmuQ= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:43 +0200 Subject: [PATCH v3 23/35] ipa: libipa: lsc: Move sizesListToPositions() back to rkisp1 MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-23-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=gU7UoVQjDoxjMvS0BE4xv1ojYvnjxU9I7ZQIqWCiBUY=; b=owEBbAKT/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dx6AsDYWVTbQexLPVjWZDH5248IuMXw2nrC q6sgtermP2JAjIEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8QAKCRByNAaPFqFW PGmLD/imCuPIfH6JVeojG7QO2zVjLe532Fyxpceb5UKj5QfEAs3VQ3KEpGA6UWER2nnmioLHfZw rWgc6NiVo1cDAI1Ur2EEN+Pe8DonTrbvCDpQQzzrpgtmpUphw+06Y+IbmPOAnvLhJSjRTRslmKG DPsifetT7EdZH1bgWVvuJy7BlZGWe8dWd2859mSgGSB+M4+UQU1ceF4wZ9jxKkHBwmPhC9qtS7H 5PsyvXYGIyYr+aDcOaFppTlb1Ol4RRrZW86bAFjkWDCNNZ9iREoipzjkc0eWP4ImEwbu0T534TV xF+HnywwGyQ12kb9UkUKWp2OSj00qRuAYvQkj306PQYiuiBS2Nhi5aZxlXStwNU0FFXvUtszqad jCD7TXjpdqc+xG+oxBrOQP4+AymQrDPAUDgzdjUHHGFELn8b4i7DtavEg3crnQViLZmepXTGTxK xZoFvQ79ewRfdPcbyfh8SD4aZ711rLFrn2h0yVNp728NkDu4IZGw9+ZGaa9CF58vUpKCLktWlEu GuN1Wm+IKJ73+cXCYDcwwWwt2BK6+04wCFzjmWGEWPH1n8xoCh22/fr+ZVgHq11Hdt4K4Dgs3LK 5gIV5qyWST0KpCZhTeDeXLluQ1v9kEGUcKKaYpiBc9q5p+vOB3GmI1GdsSAm/nbDGPb7BCRmOwg VFyVXs+PUqrLV 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 b055249c066a..7b0216b689c4 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 5c4ffc98797e..f39122963019 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 0312ce2f8bb1..f393f101b740 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 Mon Jul 6 08:01:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27224 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 3623DC330F for ; Mon, 6 Jul 2026 08:02:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DDDCB66041; Mon, 6 Jul 2026 10:02:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ifmf07F0"; 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 2075366037 for ; Mon, 6 Jul 2026 10:02:13 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C21969CE; Mon, 6 Jul 2026 10:01:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324885; bh=0T8HStG4rLCgmIX8CypsMxEEFURhdfPveKZUxkGRcQ0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ifmf07F0wxyyq+qURQghxSgy/qSnyEnZE1pzSFx0xA3mD/J+GtFq9Ds1YDak63Sqp ooDPnT/gHKSQle5BexGkWk63lXAdsdF9KvXR1eGyYw8swmROrYjfRmZcQrOshU6Iie ztqa0+wdk3+uFAhNXOsPrQHa1InP0vjpMj6oVM8k= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:44 +0200 Subject: [PATCH v3 24/35] ipa: rkisp1: lsc: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-24-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=0T8HStG4rLCgmIX8CypsMxEEFURhdfPveKZUxkGRcQ0=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DyrcZ00Gyr1Rb3TaUYiJYKVoVCxLqZXoQl7 WZmBptG9i2JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8gAKCRByNAaPFqFW PH22EACRw/ocIfXU5C1XuKJlUEFOs9wjUK4GietW1pqjK6S9neQdEFMGVIuIzbiImY1tBvsY1j5 qKBxylu3+7W5xPGST5+U7FiFB82nwm1/o/Q5Go+y4PqMkFSGoYQoqtYPIbPrnDgZmIQEalBL7+0 atkJCpx9bEbB3jIFrIlwVkXNkqH1r4f7n3g7zuA3xZOq2j5vKYvt9cKTOTi/wJ+BI2pbrvq2YEn npAPmwFUN8GuPx0j9RVAEeKH4xAZacJvztP46qJOUc8iHyarTUm9H0nsRpErRE06jm4J+2rgcnc R0qyAGCECRVO+JO51kNEmQH358vNhvU01T8wCBrLDxw+7qZFYRD9LPVHItRETXKLfRaCEPrsP/r bR8326SlUuZeGGcu8YbwhqYjbDLDwXO/+IzhSTznyi41AUs7++c33UBcSE2ktWsXBpA2VKi6C/u s9Jil3yvBvRVm57mkVZLLvzMwUK3NSCybYxAdmgWwrojYPyHN0SC7GKBNixCz6BsZqyq1Txm4Ag D+UfC5lMTQ3qpLmXtk5vyZMTgKxZ+DLpR/9OlyTpEBd4tQZi4G144puL5SYIShwnD4Zfr34qmqZ 2I8gJwWkcBkZ+ORmd6sb54TnABS8T1H7ZM6Inl3DjKEztsuhnvrEJYH5Ar2A9KCTL+Btksc0Ier u8TrTzGi06e52lg== 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 f393f101b740..828ab2f32fcc 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 Mon Jul 6 08:01:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27226 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 1C027C330A for ; Mon, 6 Jul 2026 08:02:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CA32466060; Mon, 6 Jul 2026 10:02:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Z1ahdnU3"; 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 B2E426601E for ; Mon, 6 Jul 2026 10:02:13 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 5969316F9; Mon, 6 Jul 2026 10:01:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324885; bh=rQNQ1vsGDE7PAsPQfl1To2b5CQlmApEmLmHW5x+0GTU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Z1ahdnU3hitVqu/CEY/1nscxzA7c8pd1zjPFb5RtpBUSduR4dqqiCIQXceujY2PY3 5s+LTQnAT24pxlMJ0K/3qK7wD3jHqIjQPux/BhjKNaHPedY5kMCtLlLm6qD607nFj3 2IuWB4gW8e4u96mBTKXd9Jnn1cXXdF6ujSrGRguE= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:45 +0200 Subject: [PATCH v3 25/35] ipa: libipa: lsc: Make Components a map<> MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-25-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=rQNQ1vsGDE7PAsPQfl1To2b5CQlmApEmLmHW5x+0GTU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dy7K1H+WgwuhzQUjeNTPbV5Fh9PoDGRF6HC TJL5f8LK+6JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8gAKCRByNAaPFqFW POFhEACtBGeOYpvyukDW5/CEStoCuIsthleahNu2Y+TtmfYj/Obg6OH5BAidErYJq8YRjmwzL8Q lS4biqbKVptDKTVj6DdFJTBkq0ZHZVn8iYxpnJhySOv9hCiWCyVQR+6TNASstDybBuK+P2XfHaV EI70cPGyHfFzjnc4yEs1TwGJq7LNApLNuDhZ2akZtWPUJmtiLBdvPHrBsTXVuTfBF042gxmEPQw q9mK2WDbRss/8ZBcmUWFfL404gJ1p/yxDUWWobPb2RNw583MTmcMoWpbdJlkXTXjvLZ+migjisU FAYuL+8fK3643KNCePb0vIs85IVBYKDe3lXPhNs8WIvpD9QALGjDs05Jd5xnnCn2YePIEiS6BzW 1pNYd1gF33cKzTYby/MQqzeI5W70loKDbk/wAUJ12Sok0WUAdd2FTrjlsOwNbeiSudTlgHUDIu/ ET9SGFXhsjBEe1+w3rEV0Vaqm2oJcfCFT9xeA4CsSNodGlJw87fLQ98KWA7IQeHoUw+MLAnAxsM mEFsxkKrx09WwS+oWSKmMD4LL4L9ZIc5xnjCaLalX+z0AZ/MaR6hsJke7E14XJbnqPjuwV1SbrF CjV06OtUfoPv9xHwugyKvKuKMES+2rR3mLr5xQ4DX6W5mIlvJdK/aCV5kfoGJvCYwv78RuzsVvz 6/qKjWOCnUjCXVQ== 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 66d33a2dd796..9bc71e3c73ae 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 7b0216b689c4..30af8d970a12 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 f39122963019..0550229b5152 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 41a862b92e88..415c1cc0ccd4 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 828ab2f32fcc..79d68ac7d6dd 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 Mon Jul 6 08:01:46 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27225 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 128F0C3308 for ; Mon, 6 Jul 2026 08:02:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 778296605A; Mon, 6 Jul 2026 10:02:33 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="pGuJEzKu"; 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 42F166603A for ; Mon, 6 Jul 2026 10:02:14 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EA5E11D17; Mon, 6 Jul 2026 10:01:25 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324886; bh=zVGkT/39Jp+GczcE/oZbJOtUvf76s6fOOgK1xlL6RMI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=pGuJEzKuFtP2RLuma/jlTT8ff6koTAJclXDV8gh4k4Hcfp14GAwEylO9xAGn7vmKq WnOgb3thqoTKpjFi/9KVecyWBVAZSKrZBWLoFWIeyrKoCbLsmWTin3NkWWT9bRBlBf 19v7iMSt40umGuw/VHNMGwPB3cXw8J+TN5wK4R5A= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:46 +0200 Subject: [PATCH v3 26/35] ipa: libipa: lsc: Document Components parsing MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-26-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=zVGkT/39Jp+GczcE/oZbJOtUvf76s6fOOgK1xlL6RMI=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DymOTnaQGSxj//uZOIpGlx8cRhdO0lSV/SX vLJf2zDCY2JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8gAKCRByNAaPFqFW PPcHD/wNff7t3kcNWs3ABbYoJ3VevDlFNaYavuKQUz+3yZSMoDNgN2ruKpE7O+FIk5OGALHv6Pj 1aalaJzyQuuzWW9lpkWluL+H05tQ2dcUfSjBfIuvDD6BMLiQEjIbRwP2w38v0Ln1puImQ0JT2Y1 0IWpFdqxjQCqdo7rP5+dphpYgr+Z2wvVxaIBiqdoL/3edI968lMGXeikz44paft/QquhFjEXhoC bpkhdKbGzBzdPktsHVIwsgPQJiDuo/X7YGGpdRciVsGDQoqHP4+xaAXwmG0rk5nw7L2xpYWqAfG OdPkILj94OzruzeL8ModStG0U9Ubr8AEHfnTtqXRX4sL8kokBLFlY4qQTxuJm+mfIIL6zci74Cy 2AwQ9Qxp123bPimlFaTTXx7XuGV+kjhOlj+D18OKVo6pho1jjF6JlVX8tcJUnwzyR153VZYGo2l B9OR8/bGsg0YQQok/HthRmfa9PTIK19uENfECfocfTKHbrXPswi9JsiiRNSgyH/QeIAjSQV51T5 GzUSGjYbKasMl/TZz08nbsVIVJl8NWrfOXesV2nWlcVUxyielvrKmDFVHXycd/+29O/eYNJ88KR /00UObJ2JZMJ/QIkcmp7NAoZlNaXiZabr8jm/MWlM8QgNC3Py7iC12qXbx3wqstBezNX2G49EOv F2sUW7hCv8n0CbA== 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 9bc71e3c73ae..c860569ccee4 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 Mon Jul 6 08:01:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 27227 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 039D7C330F for ; Mon, 6 Jul 2026 08:02:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 81A2266065; Mon, 6 Jul 2026 10:02:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Zj1uVw7K"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BE6A866003 for ; Mon, 6 Jul 2026 10:02:14 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 751913FC6; Mon, 6 Jul 2026 10:01:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324886; bh=NbrfAK9//1utpY5eLKzcx1k5qkbe73wBX/A1oIKCQEw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Zj1uVw7KhO8LFH2cde9kXY/6tjP1pytrGdH7MDvXzX/jLfrsP9MIzkLtvaufxLIe/ AFMNXqbXmdDotzPlK5BMSdwJ37Gn598zr2walzJZsFW/09orHLM2AKF9kZlfGXIG/N 6QF0fNZwMDsEFHDGJ4Lr5KFfgZ/l86g1kfdU5ltY= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:47 +0200 Subject: [PATCH v3 27/35] ipa: libipa: lsc_polynomial: Remove sensorSize_ MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-27-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=3191; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=NbrfAK9//1utpY5eLKzcx1k5qkbe73wBX/A1oIKCQEw=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DykSCdQ4aftyAYXZi4twXNQViCVDBHKAFel A62UBkP6qaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8gAKCRByNAaPFqFW PEExEACIQwYR+klXV/YMd7mSsQNvbxWsiHbk/EsC8rNaM+ZpFVnOUmHEg6mPz85VqR2FcQZFvTy v7vv8QN9mZpwbSPM/JYcrUowCLXWZl1VtPOMD/7uj9MKInKTcnUGNRjQTBluEgI+xX6mrzKhacf znPuBelV3ImLNvTf2SamNoxhXyfJuSVjSO9b9GHeuO+UgJIMMvbwMEc5ZrcK45vachncTMDv7Or rtuUYZ2tDb/9U2O7VdF03zbEqIVVl+iB3o4lT0gc17g9G0NuZSwYI3aCBgLIAbqHUryd3r/F/ex W9alzMs14ZZA/tWwdLIJyBm1Yhbxayj+j10K3sGr+WOVKF3FQJTtn4FcIkfDRc09NsLYNB6+v1G Ork0r8bB5afRMPL+nyibYLiWkPg+CWKFAX0P7HDPG+cqu95q4z43ZM6qZ4dgdrY6jtQzYAz5G+q MlwA/OuqhSIFvwg4Uzd9tium1iusS0TLQxahZNQ1ER42YN4V43dD3DhyTcVv0XO07UtcUjPyYmm MS0XaGRDdVMt0g9ofr6mbfdqZV4qTgn+Bt6zmQK+RIn2FZP6cJNWMQ+hnefyj3C7xP6kRDxGCHq l4GdWfBn28RNpfjY4b6dhrohanwLWVTEjHxgBrFU0SFMlcR6tNSZ+oyXmLaS6noqDPSmCFUHaVP OmOAMiuk7zahDDA== 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 --- 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 c860569ccee4..804bbc2915e4 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 30af8d970a12..7de6ee70e7a0 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 0550229b5152..013a4469546e 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 Mon Jul 6 08:01: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: 27228 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 ADF5BC3308 for ; Mon, 6 Jul 2026 08:02:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 44B7866054; Mon, 6 Jul 2026 10:02:36 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="tjO7L+bM"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 38D9666032 for ; Mon, 6 Jul 2026 10:02:15 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 06C1E1E5A; Mon, 6 Jul 2026 10:01:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324887; bh=MzlYpklOgCV3wpE9zpdKbYKUnZZIrVB8qlPQIJCccOg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=tjO7L+bMh/MXUqedH95JPg/3LHB37OStcLs8sxNWJmmGhKetQEBrYt0F6hOUNFFdR UK5XiC99G7h/CZdLDYI/H/xYCU3B+ZEWynU7u+vP/2TdK6BM/DfziyoxzqokfGv/na IsB2mfNRQMxeq+gOKQEJjnaH3Zm3pUd7uIOin4fg= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:48 +0200 Subject: [PATCH v3 28/35] ipa: libipa: lsc: Document LscDescriptor::sensorSize usage MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-28-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=MzlYpklOgCV3wpE9zpdKbYKUnZZIrVB8qlPQIJCccOg=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DykjeRzQgXDFGZKIoBfceXaeTAlMYUuIrz9 Q/5WRJHTgCJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8gAKCRByNAaPFqFW PJdoEACAVaK9EcL6z7yXbUahAwF53t/EPQ4tPMfZ9v5p3Ur5WCygF5LgG9CfgqNRFrqH7CkTgCb 9LsPfgczjcM0rmc2N3CdlwS1cUCrr/C8BP+GPfaYHm0JOuSCrka0r1Vb05bvc/EVIHFSsSLXOcC AW0cEMV38871ICMiSa524ZaiztnVN3DZF8bu6M07iUZpuvnbIlnP1qffAWQduONSx4nM29JhN5C faQW1wqcCxSiqF8UsouPFbeASw+563o65/DEWPg3Nc4jcS15k83Eza11flRCPz4z55CSrXYJVvw zKlyBpbKcP7IDVvUUYNKVpjr4GmIq/F43BhflJby5EuHQNMHLXWTE6q2c5fuDgYPIlPcOahKeUZ dctu0ApyygViOIQ6Ix/8/M4GF/XPEZtKpckOppUQB/U1zB3R5LiZbCvZVbAdYG22pqe7p8Tr0f4 PaJmF2QLduVOmc/1+1W8wZgk6fRqW6+1WdnJmRiIMZhZq4UYbzCvqhBZQUWDubDafwFqqHZJfpj hhiCCivYPb3zDHgQkWJnnjdETkAMLXTMCBN8P2Arwl70PJaWTA0qo4hwJ6rsDQSakNHpou5t1Hc jNeWhcflymI6+V08rbj1qjSKFD/MjTXrQuOHFp+bb13HQcVo5RCD/x3lz4HmslohVjV7Zq0P1wd GwHo6hn972JsMKA== 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 804bbc2915e4..c1207292518a 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 Mon Jul 6 08:01: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: 27229 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 6887BC3318 for ; Mon, 6 Jul 2026 08:02:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 06F8F66069; Mon, 6 Jul 2026 10:02:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ETRrjvWP"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 07AB266031 for ; Mon, 6 Jul 2026 10:02:16 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7C4A93FE8; Mon, 6 Jul 2026 10:01:27 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324888; bh=WEvfaM6MO/uOsVIRRFYs+XFPvBWUs8JbZ1zNEbDqhtg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ETRrjvWPTlPo1XM181ztjhecX29DQYBBmTwgfoRnCCQySmWmaSRHatuDEFxJlA+LB unH7csiZbR49Sm9mWGrjeCuMRqTHDxsh6igGgx+ihkgVX06W9xrrEWOg7ixL5xq3E8 ZfO4A6FSmOesnLAzjveevq23aYqy8R0y4zSpAD1U= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:49 +0200 Subject: [PATCH v3 29/35] ipa: libipa: lsc: Template LscAlgorithm with register format MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-29-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=23337; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=WEvfaM6MO/uOsVIRRFYs+XFPvBWUs8JbZ1zNEbDqhtg=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DyJunQgTeZ6lynttiVkuB0bLgDO1dtzP7qo 4tlNCMksViJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8gAKCRByNAaPFqFW PEZwD/9CeRXfVdiLB3FlTRA+Sn+50Wssqo8i7bAjP9F5/V1Yebv3i5UY7SlTau6Nv02ccF2TYbY GZE7/oyl4ygbhwnurterckIPlHTUxs0BI/SCDCdwxShwkEt6s9HgGo4nFl4tVbTx2Y+zEBg1RhX Ek53lSGqY41cYvayKvCesDCfqPqvskliXog8sYrM+furZRpdRqcLiD3pva39tUlSQEq3iFyfOWA UesN0AXZ7xDgn53iPu6gS3q+1nlhOyM9tuCQ24Sg/GSeKnxWIrgJkGLxaC+D5IUHuPbDOahtuuO hHlt4s0uPUFg28LPgfiWWMl+a+H3hRbrx8Utn/L7GRtzGierdXOnqkX8fWqu6zNAcbCAt5MDZAh tMdciATF3O1aHMeagLaLM/28+anFQgHuvBMxM9/aRpQpcGbW9AzCuULwfXvvkg2aLVE7QkL9tQn 0su1eZruu4dnB1QCKwBSh6h9bkkOauKWC4oSGL6bnQX1VfTDjYliuGBZyNUq9ldihWPGxP3tHZs 8T+kKZvVkd8VvciJIsYWi6+RFgnSYEB8ewP67nxkY3aNH1zS2DwsLT9/ESvsAvvJeWVsbXd3yNf +IMF2/W6c22/AmQzwt7u450jNsmMzprqvsskz5w/AjOfY6icxLR9BouHDd3+e882IZLTdp1lM2V cRDaOoigA+azhXw== 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 c1207292518a..621c04d354a8 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 7de6ee70e7a0..0eeb99f6881a 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 013a4469546e..2b743ed53be7 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 + * \class LscPolynomialBase + * \brief Base class for LscPolynomial * - * Polynomial-based lsc algorithm implementation. The LscPolynomial class - * implements lsc support using Polynomial to represent the shading artifacts - * map. - * - * \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 415c1cc0ccd4..bb3c76b12332 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 Mon Jul 6 08:01: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: 27230 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 379B8C330A for ; Mon, 6 Jul 2026 08:02:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DC6B066060; Mon, 6 Jul 2026 10:02:37 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="kUlFu5Ct"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 406C866024 for ; Mon, 6 Jul 2026 10:02:17 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D46B83FF5; Mon, 6 Jul 2026 10:01:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324889; bh=wb0bQGsjGojAwnPuRxkfgTB4xkOkV0ZPsl0VfiBQO/U=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=kUlFu5CtDo7/qbFZZ44J1E07X+Bto85HIMcboQx6RagkKCTNbWMlHom1AdhuQPqQO 8O2SQANQEhS+4AveB6HUtYanc2s9kXQPiwqT3aXzo/eRoVi1CTivjfN32p8ISVk+IU +9hHJVPBDXOw7TfQECVugsMnrH/1y5rxBxpNIAbg= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:50 +0200 Subject: [PATCH v3 30/35] ipa: libipa: lsc_base: Describe the function of lsc_base MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-30-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=wb0bQGsjGojAwnPuRxkfgTB4xkOkV0ZPsl0VfiBQO/U=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DysL4dkzzQCpaTw9z+c9CFa3zPozBKTQr47 zYDs71Q3NuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8gAKCRByNAaPFqFW PKE1D/40qaGfQ+3tYWk+WWLD6NBnXBQ9s6Rk0hRQFPAdBEttwL5KDM/FSO/Kmhc2LH9WyUjrUzp 0XhGuJCxdP8pKaj8D88C3h6IrIF6Wk1UTcRnHlcw7E7XPuUrulZ0vj8mHhRMP5y9sD5Kbm0RR8X kWI6aEXB2Xf/aDr0bluVpK2o7p6+0LOPsn3DxcQ80WOxSCK8XJMXfRp189zhKiSF+2zi6IYG9me 7xA3yDZQU2ZvPKUTh+VqdArdqnkYQSYqILCWilgvNvzcsp8eI6hQqnHn8TNEKIlDJx13w9fliYL Ql1nUURk1KoHZMhi8xkPyoAVH5FpIhPqxc5QA7Pf/G1148QH6CNmRjTbANV+ookWlAHRfKuEHfj +OH5yR3TIzHci0vNdULHJR5rxYaFmGphT5j2N5oLsjJjk4AW+GymhaXh3xqE0rmYhjs7jpqQdq3 ZXP3/Hpznp6XaYREnh5Gs3fs/El3sAk3Lgj6ZJr1EKAfcoXkBv3vLSaAjtK+x2quvZkGqFr8UC1 uj2SX01FTaU2xqIR+I4tJw9g2N3IjQW/fwWbIqImfDyoSAM3iK9egTkNeb+FL7kW2UpXJ1fcFGn GX5TKrdJNPDXgm51pdCtnc5ptTCSfQAEZ+JkbctdOdL1zQykKZdoqOY7i3nLqFl6axYAeyI6OAs 0zZecvnh1t3NBZg== 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 0eeb99f6881a..0c3a10d25cf8 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 Mon Jul 6 08:01: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: 27231 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 2BDF0C330F for ; Mon, 6 Jul 2026 08:02:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A95116605D; Mon, 6 Jul 2026 10:02:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="oFqWly+J"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 96CEB66047 for ; Mon, 6 Jul 2026 10:02:18 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 73031226E; Mon, 6 Jul 2026 10:01:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324890; bh=3KIg97egNK9A9fKHvykVfRGsaB2SQbUBinIuZuNuJfo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=oFqWly+JexaSG3IDqI4O7PqM9YhCgD6yyu5cK1I7Eaef8z6cfRiLBAvED2Ar6toR1 SXYnysM4pTDB6rKyn9FNjLiC5g8X+vEZ6d9rye0z9J0NqyS8T75jmoOj2femICut67 JVaqXY1DluwpksBXQP/QkBGZEjDJ2zXDNL/0XbSg= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:51 +0200 Subject: [PATCH v3 31/35] ipa: libipa: lsc: Template lsc::Components MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-31-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=3KIg97egNK9A9fKHvykVfRGsaB2SQbUBinIuZuNuJfo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dyy9Oihpw94fqFMmhJU9oQjbL43I78MZjOB 26PQHSadz2JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8gAKCRByNAaPFqFW POuPD/oDSbJsGX5SwbPBU4xNicL8FLUg9csVwO1KG38ugw3RVv+E33vLnhrmcjQwoC4+6Ns91th erT9Xv+/jn3abW9wzfzC74XdGZtzP78aIeWOQRgI8wR2ZzwnuIxVwrirXeriG7pa83ijNkQ9SSw naV9bFqK1gsZv27oZKRSFeP8bZFWC4MDDMYuFs0AT4b2onSSbCM4UKm8Tw7lApuk8JXXWs4/3aC Vo2oTiCHpXfPwEqTdsNozUFU2oZK8Wgruv+PcYo9cmWdqvVByVVoX1tKvk1vA6UfEJWFW68dyep 5vHvlOfl92dPyDHUjVyYJdzrKhTOipIXWtbvKUdi9CkT3tkPlWH2hw3IFsv5GckFgO8Uav7Avp9 IUCb5h2ayB45EbLnfgyYA/B8wKTDV5S+v2exitDT8olg/TthyxGf29/f7J7mjISEUs4onw1ZJaP emXnLNYv44KMP/JLtRuMoxJx5ds5W5wytY/84wqn6+A4FKhOJfN/I1k9YHJHqhH11w0pa9nE6+j dIf4TdhxkH2QnJPHYQWNbXsgWRc2zDYABHubJMuKon4LBW/jBcHq02+1aUGxL/rMUAGNH1gH5Ct nqNXMTTKhj//UtDGa82thVrNxKS868jpFjV0rqocSSo3nQOpHvA1GW+Jpi1NjsR6dCv3ulm6Z7E hCqY2IzL85DJqwg== 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 0c3a10d25cf8..ff5d47e85c01 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 bb3c76b12332..aa391534335e 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 79d68ac7d6dd..9e3dc517f6cb 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 Mon Jul 6 08:01: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: 27232 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 EFF66C331D for ; Mon, 6 Jul 2026 08:02:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9E58B66054; Mon, 6 Jul 2026 10:02:39 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="SODxA9nN"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3A7476603B for ; Mon, 6 Jul 2026 10:02:19 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C03F23DCB; Mon, 6 Jul 2026 10:01:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324891; bh=dOfbZ8LZPhwdvMfBunOfkCUHa7YYqHotTtInI7cV5ow=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=SODxA9nNwaaTzKDtc8HYP3wAG7/NhF04E/b69dfnC3i2lMWlsUNywSjA90IFz9dXw 5hA+2zLE4GaFBYkHyvTYtHJ+RzzTT8DLd9r0pUPGyPJInhBnjF2ahJ9TG7pbMhY59y KPv7hi11JnQ+7lyPy1KI76hwOCxMTusOhGe/j4hA= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:52 +0200 Subject: [PATCH v3 32/35] ipa: libipa: lsc: Rename sampleForCrop to resampleLscData MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-32-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=dOfbZ8LZPhwdvMfBunOfkCUHa7YYqHotTtInI7cV5ow=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DzUHPQ6dAjqDRKSsPiDXv7skAw4xtLQ4nEV W+G2RMM0qqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8wAKCRByNAaPFqFW PA8vD/9db0jp7gg1FhMHp2r7sW6WtDPR6SpUTRk/HXOZUnBBjDxx6h92PJ2ut3cxcXCn/4iMOHF Pm18OjWclhYwFdc4QzAQ3i454BDggWn444Ij60WgiJH1ggtfbPbCNE1X2sJ8hKoneRdMVjQWpVq gC2j6O0IxJnsTLNbeEj4Pd2AFYO/E7wQnVAiLMxQLyIkoSniLXXbcOYiQDu/Dir4wZM0fUQtC4u Xfy/FJwB9llrGk2TXrPfU8jF2HDKCGyqWfqBZghm3ACcLfyDpMLjht5YY8Wskuuff4VuImkwpQg Ue1gmx28z+7HyQ4TSNo85Fkt2+syDhAglEQoaAB05xhoTmtzWk+k9A8PDbHEzz4BL6sS2rD30BK 5RDWtfcbF3vBVWbCpjsvNqg2fwXGqVQd9G431+AwQmuUBTW8TzTrXqMlPaofJ2xIBFi8iEPoYso 2FfMKP9lpYQcDBZjLJXkfPYMyXI5HxRFv6WkJcoiqSIcMl/4636KsPpbMA0u2PdRCGZ5dWzB87W UWmjcTSLzsVdenIpqGEZO1FoaM9JLq8wRYGTRDgym95U51HinT9XMTau9NIofO/OoKvgIub7IwP rrzgZxVkNMBP0s5Vsi9jk+nuJZD4IhTnEYc1MwUSbVoqN2GVePA6vjmkqAXMZ4KnXNERoa2RT8N /cP4a8MhB/+ziQg== 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 621c04d354a8..d1dc2524fcaa 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 ff5d47e85c01..f47c9619669e 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 2b743ed53be7..7b3c13cb144f 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 Mon Jul 6 08:01: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: 27233 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 A9043C3308 for ; Mon, 6 Jul 2026 08:02:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5D2E366071; Mon, 6 Jul 2026 10:02:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="tmvWBUF5"; 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 D558466035 for ; Mon, 6 Jul 2026 10:02:19 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 79A243EB5; Mon, 6 Jul 2026 10:01:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324891; bh=V4MaF45qbZhRRiHabRO68RMnvgHcBtsHCDtfQ962rIU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=tmvWBUF5w0+NJgvwozZjt/qhR3T/5dVXXoC0yDTIp/k9Nng1hFaIz18C2TAOcVbky 8at3UPYQT9afEF7ogJHizW1j2BYIL3mTUrloIGfrkcUdojjp3thcuRE+upvdRcWiVt sGCtSvsDfUTLed4Zd+rogDo++pYkV7mWo2OscLqI= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:53 +0200 Subject: [PATCH v3 33/35] ipa: libipa: lsc: Specialize Interpolator for uint8_t MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-33-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=V4MaF45qbZhRRiHabRO68RMnvgHcBtsHCDtfQ962rIU=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2Dz8wOzUM/vbrbq8U8GYIx5VplZAn3QJHgB1 54zIBFVUiqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8wAKCRByNAaPFqFW POsQEACrM8/Q1t7nUZO7z2hsLj+my0Q2hvTODDindgikwhrzFJ2AoKyJIoYUXjSi5z4VxdcK2VQ kWStp4GCOpw8ionp769XJWC6AQMwLosBmZZUTuBzD+eJD5hqnyDohGBywUbkF9WeWy70+0pQWu8 XUFSkPTpl8fdyoxmm6rp8X2JqGI6iiKvD0DvbKGXJedABNHXaWhkfu4V2ZjEWlr13O6QETFSRyd jdMSo70Hn2H781uRL5/KsYoCpIr496DGlkmqLaPIb2vJVkXFok2BTcgdxa1ueBYYFBcNmFsoiMI uuP/i5GNNFfZr0quOMqdgajPYRQKjRBBJhU750YFvMqrV/AIGDtilEb2rzzer8ESXEwr83cBa74 9ywpUpStY5q1dGLcrygvjRNlh80Eow1QA5KZnToIkFxwJS0peUqPnF/ys8dcd4EopFnE3es008f YUfDyxqEmwsDb0JU4yl++UecZAut9icj54NPIN/h8C0MuIxebOsNYa2ny6uo4eJRHl4WQCZI+G6 BnRLSJXBCjpSufYMfdZ/p/OgDSMwU5CD7+GeAZbJwR/ef/5OAaLuv0nIhefiTM80uADgNTwyE9H RMwemdrFKImk/w7fH/ylW/bE2b+D2niUYF/XTwI2ZERl2BUQQXA34BOJge8jGPgfC0uQqGqu3Rk BhtychCyAGJB3nA== 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 f47c9619669e..3ab1c75c4310 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 Mon Jul 6 08:01: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: 27234 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 6BADFC3323 for ; Mon, 6 Jul 2026 08:02:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1BD6766073; Mon, 6 Jul 2026 10:02:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jCe1aAI6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8832E66048 for ; Mon, 6 Jul 2026 10:02:20 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1587A1CE4; Mon, 6 Jul 2026 10:01:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324892; bh=qslE62H4MVVXTmR6d6pRKpz1V/AFGEiJNe4OSZxTik4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=jCe1aAI6mnroHnFThSgw6d7eIgbcfNvbWhCq/LBChfy+RVRS00h4wpn7eI9NNYgt3 Npaa45KV51z3GeY0KDld6ZhGh4kNiwLIRfclPL2b5F3Png8z4+cHKa9lTjlQ695Hpo UslxBk3uORnVcQ/9lA7T/9MZH4FNu462AWp38o20= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:54 +0200 Subject: [PATCH v3 34/35] ipa: mali-c55: Add sensorInfo to IPAContext MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-34-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=1599; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=qslE62H4MVVXTmR6d6pRKpz1V/AFGEiJNe4OSZxTik4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DzrkNoJWETb4eA4IuFTx2r2vwZr8ygnDpMr cIpmfzvBIuJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8wAKCRByNAaPFqFW PHSoD/9AHUoxhocgE5Lkh/Yb2wLxAoj9mF/cf0sTVcHN6Yo9zCG7TVh1XcoDeWW3kkVuquo1CF6 NOuh8sBsbLS+VSBkk000YI92tr5Gi2TyeSXniOGFhivPd+w1BqC14x/AE4a2YqCOV+uVi3kEhwH nPTXlAMwJUuP+fvL9GLWi1uCYBhqVSwM5qFOikEDagHMENBUcrdUwSq7vrao1wlDNxarROJG1PY I0hy35BExQS0JGELCTpyarbA5uzCWZoKA43iOZmxWq04be2oqSfHnRx7uXiiIw7f2ZOXr540u5d zzJub1LuyPFu2O9aANsq6PStih3c0nQwLnnsX/gOQ21sa3sOUAejVkNlQ05cbcgeepU0g9HhsZu YC+tyY+vC5/T6GDPHYx5nhIRJIpP5dddsVCKf9ibH5fhPgjXEJuaSqc3a5NnrND9s8keEfSRCok fXMhpR1ENXpcKW4WiSbqwqf1yTMFQSr2JlJh7cy8bVdMYCF07j/wLmC5cuKwQQOR5F7NaU8PWtB EpnqcbzxYm5NLNZdIcIoUIZ5F87pio1E43mRt8GRuihpJNzYHXljGOCvD0lOE+v2hiVOyC1zE4C tOCN04buJ6ZYngTd6ML5/t1Xp5epLky8dNXDbLOWelPJolkSCd+crAhT6rMCulwKh0ELY5nmVA1 Z2BLtMmY+ye1p6A== 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 aae4a543c1b7..0daa5f8023a2 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 @@ -78,6 +80,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 c35cc10bea01..1e49f8bfd5ca 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -98,6 +98,8 @@ std::string IPAMaliC55::logPrefix() const int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, ControlInfoMap *ipaControls) { + context_.sensorInfo = ipaConfig.sensorInfo; + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!context_.camHelper) { LOG(IPAMaliC55, Error) From patchwork Mon Jul 6 08:01: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: 27235 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 4B86FBF415 for ; Mon, 6 Jul 2026 08:02:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D8D2066060; Mon, 6 Jul 2026 10:02:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ut2Zd32r"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 905AE6604D for ; Mon, 6 Jul 2026 10:02:21 +0200 (CEST) Received: from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it [109.113.60.81]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C79843F8B; Mon, 6 Jul 2026 10:01:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783324893; bh=f1UqyIzI2T7DnX6z/Cnua71GzNArb08yYpmFTSX6oFQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ut2Zd32rhTolXBX6+LTURPCqg4dVWI4PEpajNJ5UCxja4tQqUJkT2czM74ay6MZDE 0f9kCNhHEmDAbwlkY9pCYB7aUyjtVOjM7jjbCUjqLKsKJ1Ncd1gPcOnKuG6D6vruch zLx18KNqK6ocIIx5h+i+m76WuFCX4euZlVDIlkxw= From: Jacopo Mondi Date: Mon, 06 Jul 2026 10:01:55 +0200 Subject: [PATCH v3 35/35] ipa: mali-c55: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260706-libipa-algorithms-v3-35-968757b038bb@ideasonboard.com> References: <20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com> In-Reply-To: <20260706-libipa-algorithms-v3-0-968757b038bb@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=f1UqyIzI2T7DnX6z/Cnua71GzNArb08yYpmFTSX6oFQ=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DzfuiMSsbV7ENzOuoA36F8YD4KLI81QsPxI vpbBCmEU0WJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg8wAKCRByNAaPFqFW PHf2D/0ZYaHc1ycNYddqGCHdSelqzMT5HYCkOHZWzKROBvZOD68Mucwk15STELVLSkKz3CJfY/u YUdO0vljZhp/FgzzLmD/P3hfNp0kwU6mNcRV6L3172FQ7Bb+pnTdg2sJ0G0KPY+2JCrsxGwO2TQ wtapzvR5TF5YkBiZMVFdLzQ/X9C2NrUR95mMhaOnN75K5MSs7qQsjCR/TK24MJL4QdJQ2F3ki8R OiQaHTtpdWN7biKY9Rrs5JrAbsOMAAvVP+I0HVGXd2FvftdDXllXsq/2RKWXq4U2viNEWbpxSJf Inf7CLpIus02eMyezhSAkFrBgMXi+96KL9MQWIeNY8uNp8Vdby+WwpeKtneOMnSLH1l/3XRa19e nIMX51xEh5BVSHoE2JSFDyC5jSzwYDvjIkhSd4Q0ms5/5dQKdVK+ynyVa8EcpP8IXbNWiAFcCV0 wZrYpnnxqHXRzj0Yna2qSgvtwSFcaJ/ngPVIitldu5J0zaW/S6y6SV3AKVd9cxhNmzQZSfTItF1 iLKtI1p1iK38AM6BseAA2JVk1I703qKQ4ifmTaXKleAcaOKgPm1Z32/OqmqAqleL4JSs/xBGiPK /CPV84sqWHDAqul0cLkCSE4OTyIPYFFdXvIRBBXu15+GB/YM7VWkPj7bG6I8e9BAxAWROnxZwe2 JSM8x8GGtLn3w5w== 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 0daa5f8023a2..d924fb7ae7c0 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -20,6 +20,7 @@ #include "libipa/awb.h" #include "libipa/ccm.h" #include "libipa/fixedpoint.h" +#include "libipa/lsc.h" namespace libcamera { @@ -61,6 +62,7 @@ struct IPAActiveState { ipa::awb::ActiveState awb; ipa::ccm::ActiveState ccm; + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -72,6 +74,7 @@ struct IPAFrameContext : public FrameContext { ipa::awb::FrameContext awb; ipa::ccm::FrameContext ccm; + ipa::lsc::FrameContext lsc; }; struct IPAContext {