From patchwork Mon Jun 15 14:05: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: 26878 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 9F01AC324C for ; Mon, 15 Jun 2026 14:05:54 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8631B623F7; Mon, 15 Jun 2026 16:05:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="JYy2FNsa"; 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 51FC5623DF for ; Mon, 15 Jun 2026 16:05:47 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 343F8227; Mon, 15 Jun 2026 16:05:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532314; bh=uEp9/WTPzzDC0CH0TgkGl1QFfqOX3F64c0recN4hoq8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=JYy2FNsaSS3RAtrJwg+m6v51g0sb+W8D0KUoNTFLsKWPPHUTelU/9WZEHGZ+IxeGh ngavINcmF1lhKUGPWul+YHGcW+H7iFim7tYE2m44RWsUBU2cmqpFUE4lrPag+PAPSe vDNwzvPpMFhLZ71u+JbTFcGo/4O1XcRo5iXMAUAQ= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:26 +0200 Subject: [PATCH 01/11] ipa: libipa: awb: Reimplement AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-1-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=49306; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=uEp9/WTPzzDC0CH0TgkGl1QFfqOX3F64c0recN4hoq8=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa4duQFA7ohHHtCgl+kuQeGOFmE5SkLY9vkF ByGbB0LowaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuAAKCRByNAaPFqFW PHOaD/oCm2SImAH0WSEDDOrawpJPDvsbxW2FFHTcIqV36JjTdFCWbo98kU4Wv3/uBbzdXIv9t0l G+Ca9GGLVd9cYRhgBVLZKPVwuURGazq1OXTewsFgTIvuOAsHdE28RVmbgmZrrnbQRfub4KbMcF6 VTDwjWZXD2+GUhc4cLfUL1U8P7+TsDzNuqWoeOVuJn2VQlMbOwcWrO/Hv9pwqfZWfrt7whOFlGP W1IfMxIjkAI2248ce7fDYWRoKjk4j0EKDFbXiP+nHVsHpQHuYvrHnQV6mPQSZ1EqgLPsXehX1N1 WgQ/VPmgIchJmZtR6AzRgIO9jml68i8NEdT1TIM3XyaIUE6cLTIC3KysJwXepRNPDYi6iLKZfOh j3axc2D6y/ZFNYmzA1hs7C4tPazPk7IiSs7QqXdcjI1ghcvI9pJczO482slXEef6d2Xu6OLk1Me Dal7oFD6PmmHbTV37M68/4u20rdJC/BisSLXvtbgiarIOx9ttWyl+aq4qVz78mmiy7bq6UNRpIB zx2zw1YANkt7zEBc6bmQWvdzwFjAaRljigBZC5epoCrNrNXsLuMC90poy9TOdkOPIWSv4+wTuY6 2AwEWMEAtqR8C8Jy+3sgBeTkHQEO+dJdiFDW8p3LfECxz8wvXlVo5LDDkR10H+CzPpoY6qi7Fmt Rsw3uTwlkeUrw/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" 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 what 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 --- src/ipa/libipa/awb.cpp | 562 +++++++++++++++++++++++++++++++++----- src/ipa/libipa/awb.h | 123 +++++++-- src/ipa/libipa/awb_bayes.cpp | 40 +-- src/ipa/libipa/awb_bayes.h | 11 +- src/ipa/libipa/awb_grey.cpp | 16 +- src/ipa/libipa/awb_grey.h | 5 +- src/ipa/rkisp1/algorithms/awb.cpp | 225 ++------------- src/ipa/rkisp1/algorithms/awb.h | 18 +- src/ipa/rkisp1/ipa_context.h | 28 +- 9 files changed, 679 insertions(+), 349 deletions(-) diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp index af966f1e007c..b9f7d4004cb1 100644 --- a/src/ipa/libipa/awb.cpp +++ b/src/ipa/libipa/awb.cpp @@ -2,18 +2,24 @@ /* * Copyright (C) 2024 Ideas on Board Oy * - * Generic AWB algorithms + * libIPA Awb algorithms */ #include "awb.h" +#include "awb_bayes.h" +#include "awb_grey.h" #include #include +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,34 +28,88 @@ LOG_DEFINE_CATEGORY(Awb) namespace ipa { +namespace awb { + /** - * \class AwbResult - * \brief The result of an AWB calculation + * \struct Session + * \brief Session-wide awb configuration * - * This class holds the result of an auto white balance calculation. + * \var awb::Session::enabled + * \brief True when awb processing is enabled for the session */ /** - * \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 ActiveState::AwbState + * \brief Awb gains and colour temperature + * + * \var ActiveState::AwbState::gains + * \brief The white balance gains + * + * \var ActiveState::AwbState::temperatureK + * \brief The colour temperature, in Kelvin */ +/** + * \struct FrameContext + * \brief Per-frame awb state + * + * \var FrameContext::gains + * \brief The white balance gains + * + * \var FrameContext::autoEnabled + * \brief True when automatic awb is in use + * + * \var FrameContext::temperatureK + * \brief The colour temperature, in Kelvin + */ + +} /* 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 Construt and empty AwbStat + */ + +/** + * \brief Construct Awb statistics from RGB mean values + * \param[in] rgbMeans The RGB mean values + */ +AwbStats::AwbStats(const RGB &rgbMeans) + : rgbMeans_(rgbMeans) +{ + rg_ = rgbMeans_.r() / rgbMeans_.g(); + bg_ = rgbMeans_.b() / rgbMeans_.g(); +} + +/** + * AwbStat::~AwbStat + * \brief Virtual class destructor */ /** - * \fn AwbStats::computeColourError() * \brief Compute an error value for when the given gains would be applied * \param[in] gains The gains to apply * @@ -57,87 +117,407 @@ 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. * * \return The computed error value */ +double AwbStats::computeColourError(const RGB &gains) const +{ + /* + * 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; +} + +/** + * \brief Retrieve if the awb statistics are valid + * + * If the colour mean values are too small, the AwbAlgorithm class doesn't have + * enough information to meaningfully calculate white-balance gains. Freeze the + * algorithm in that case. + * + * \return True if the awb statistics are valid, false otherwise + */ +bool AwbStats::valid() const +{ + double minValue = minColourValue(); + + if (rgbMeans_.r() < minValue && rgbMeans_.g() < minValue && + rgbMeans_.b() < minValue) + return false; + + return true; +} + +/** + * \fn AwbStats::rgRatio() + * \brief Retriev the Red/Green ratio + * \return The Red/Green ratio + */ /** - * \fn AwbStats::rgbMeans() - * \brief Get RGB means of the statistics + * \fn AwbStats::bgRatio() + * \brief Retrieve the Blue/Green ratio + * \return The Blue/Green ratio + */ + +/** + * \brief Retrieve the RGB means values * * 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. + * used by the AwbGrey to calculate the gains to apply. * * \return The RGB means */ +RGB AwbStats::rgbMeans() const +{ + return rgbMeans_; +} /** - * \class AwbAlgorithm - * \brief A base class for auto white balance algorithms + * \fn AwbStats::minColourValue + * \brief Retrieve the threshold below which a colour information is not valid * - * 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. + * Mean colour values as reported by the ISP through the white balance + * statistics are considered valid for colour gain calculation purposes when + * they are above a certain value. The awb algorithm needs to know what is the + * minimum value below which it should ignore the colour mean values. + * + * As different ISP platforms report gains in different formats, the threshold + * is then platform specific, and all IPA implementations that use the + * AwbAlgorithm class should provide that information by implementing this + * function. + * + * The reported minimum value applies to all three colour channels. + * + * This function is used by AwbStats::valid(). + * + * \return The minimum valid colour value */ /** - * \fn AwbAlgorithm::init() - * \brief Initialize the algorithm with the given tuning data - * \param[in] tuningData The tuning data to use for the algorithm + * \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 * - * \return 0 on success, a negative error code otherwise + * The AwbImplementation class defines the interface for the awb algorithm + * implementations. + * + * It is currently implemented by the AwbGrey and AwbBayes classes. + * + * The interface defines an init() function to initialize the algorithm with the + * content of the tuning file and two function 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 interpolates a gain curve + * if specified in the tuning file with the supplied colour temperature. This + * function is used in manual mode where it is expected the application to + * supply a colour temperature and the algorithm to adjust the white balance + * gains to it. + */ + +/** + * \class AwbImplementation::AwbResult + * \brief The result of an Awb calculation + * + * This class holds the result of an auto white balance calculation. + */ + +/** + * \var AwbImplementation::AwbResult::gains + * \brief The calculated white balance gains + */ + +/** + * \var AwbImplementation::AwbResult::colourTemperature + * \brief The calculated colour temperature in Kelvin + */ + +/** + * \fn AwbImplementation::~AwbImplementation + * \brief Virtual class destructor + */ + +/** + * \fn AwbImplementation::init() + * \param[in] tuningData + * \brief Initialize the algorithm by parsing \a tuningData */ /** - * \fn AwbAlgorithm::calculateAwb() - * \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::calculateAwb() + * \param[in] stats The awb statistics + * \param[in] lux The estimated lux level + * \param[in] ranges The colour temperature search limits (AwbBayes only) * - * 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 (for AwbBayes only). * - * \return The AWB result + * \return An AwbResult computed using the new statistics + */ +/** + * \fn AwbImplementation::gainsFromColourTemperature() + * \param[in] temperatureK Colour temperature in Kelvin + * + * Calculate a new set of colour gains by interpolating a gain curve (if + * provided in the tuning file) with a new colour temperature \a temperatureK. + * + * \return The RGB gain values adjusted to \a temperatureK */ /** - * \fn AwbAlgorithm::gainsFromColourTemperature() - * \brief Compute white balance gains from a colour temperature - * \param[in] colourTemperature The colour temperature in Kelvin + * \class AwbAlgorithmBase + * \brief Base class for AwbAlgorithm for non-templated functions implementation * - * 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. + * Base class for AwbAlgorithm where non-templated functions are implemented. + * IPA implementations shall use AwbAlgorithm and not this class. + */ + +/** + * \brief Initialize the algorithm with the given tuning data + * \param[in] tuningData The tuning data to use for the algorithm + * + * Parse \a tuningData to initialize the awb algorithm and register controls. + * IPA modules are expected to call this function as part of their + * implementation of Algorithm::init(). * - * \return 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." + << " Default to 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 + * \param[in] session The awb session configuration + * \return 0 if successful, an error code otherwise */ +int AwbAlgorithmBase::configure(awb::ActiveState &state, awb::Session &session) +{ + 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; + + session.enabled = true; + + 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"; + } + + /* Only AwbBayes registers the AwbMode control. */ + 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.max(gainmin_).min(gainmax_); + + /* Smooth color gains adjustments. */ + double speed = 0.2; + double ct = awbResult.colourTemperature; + ct = ct * speed + state.automatic.temperatureK * (1 - speed); + + state.automatic.temperatureK = awbResult.colourTemperature; + state.automatic.gains = awbResult.gains * speed + + state.automatic.gains * (1 - speed); + + /* Populate metadata. */ + metadata.set(controls::AwbEnable, frameContext.autoEnabled); + metadata.set(controls::ColourGains, { static_cast(frameContext.gains.r()), + static_cast(frameContext.gains.b()) }); + metadata.set(controls::ColourTemperature, frameContext.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 +536,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, +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 &yamlModes = tuningData[controls::AwbMode.name()]; @@ -227,37 +615,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 implement 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 instantiate 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..622640318d5d 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,130 @@ namespace libcamera { namespace ipa { -struct AwbResult { +namespace awb { + +struct Session { + bool enabled; +}; + +struct ActiveState { + struct AwbState { + RGB gains; + unsigned int temperatureK; + }; + + AwbState manual; + AwbState automatic; + + bool autoEnabled; +}; + +struct FrameContext { RGB gains; - double colourTemperature; + bool autoEnabled; + unsigned int temperatureK; }; +} /* namespace awb */ + struct AwbStats { - virtual double computeColourError(const RGB &gains) const = 0; - virtual RGB rgbMeans() const = 0; + AwbStats() = default; + AwbStats(const RGB &means); + virtual ~AwbStats() = default; + + bool valid() const; + + virtual double rgRatio() const { return rg_; } + virtual double bgRatio() const { return bg_; } + virtual double computeColourError(const RGB &gains) const; + virtual RGB rgbMeans() const; protected: - ~AwbStats() = default; + virtual double minColourValue() const = 0; + + RGB rgbMeans_; + double rg_; + double bg_; }; -class AwbAlgorithm +class AwbImplementation { public: - virtual ~AwbAlgorithm() = default; + struct AwbResult { + RGB gains; + double colourTemperature; + }; + virtual ~AwbImplementation() = default; virtual int init(const ValueNode &tuningData) = 0; - virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0; - virtual std::optional> gainsFromColourTemperature(double colourTemperature) = 0; + virtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux, + std::array ranges) = 0; + virtual std::optional> + gainsFromColourTemperature(double temperatureK) = 0; +}; - const ControlInfoMap::Map &controls() const - { - return controls_; - } +class AwbAlgorithmBase +{ +public: + int init(const ValueNode &tuningData); + + int configure(awb::ActiveState &state, awb::Session &session); + + void queueRequest(awb::ActiveState &state, + const uint32_t frame, + awb::FrameContext &frameContext, + const ControlList &controls); - virtual void handleControls([[maybe_unused]] 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..c740663fa381 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,6 @@ 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; - } -} - std::optional> AwbBayes::gainsFromColourTemperature(double colourTemperature) { /* @@ -283,7 +258,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::AwbResult +AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux, + std::array ranges) { ipa::Pwl prior; if (lux > 0) { @@ -295,7 +272,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, ranges); double r = ctR_.eval(t); double b = ctB_.eval(t); LOG(Awb, Debug) @@ -319,11 +296,12 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux) return { { { 1.0 / r, 1.0, 1.0 / b } }, t }; } -double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const +double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats, + std::array ranges) const { std::vector points; size_t bestPoint = 0; - double t = currentMode_->ctLo; + double t = ranges[0]; int spanR = -1; int spanB = -1; LimitsRecorder errorLimits; @@ -345,14 +323,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 == ranges[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, ranges[1]); } t = points[bestPoint].x(); diff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h index 1e3373676bc0..fdf55dcb553f 100644 --- a/src/ipa/libipa/awb_bayes.h +++ b/src/ipa/libipa/awb_bayes.h @@ -20,22 +20,23 @@ 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; + AwbResult calculateAwb(const AwbStats &stats, unsigned int lux, + std::array ranges) 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, + std::array ranges) const; double interpolateQuadratic(ipa::Pwl::Point const &a, ipa::Pwl::Point const &b, ipa::Pwl::Point const &c) const; @@ -50,8 +51,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..76c8a2231ac1 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] ranges The colour temperature search limits (AwbBayes only) * * 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::AwbResult +AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux, + [[maybe_unused]] std::array ranges) { AwbResult result; auto means = stats.rgbMeans(); result.colourTemperature = 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.r() = 1.0 / stats.rgRatio(); result.gains.g() = 1.0; - result.gains.b() = means.g() / std::max(means.b(), 1.0); + result.gains.b() = 1.0 / stats.bgRatio(); + return result; } diff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h index 154a2af97f15..ceeee237cb30 100644 --- a/src/ipa/libipa/awb_grey.h +++ b/src/ipa/libipa/awb_grey.h @@ -18,13 +18,14 @@ 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; + AwbResult calculateAwb(const AwbStats &stats, unsigned int lux, + std::array ranges) 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 5ae5b6471643..be0caaf7bd40 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,54 +23,29 @@ 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() = default; + RkISP1AwbStats(const RGB means) + : AwbStats(means) { - rg_ = rgbMeans_.r() / rgbMeans_.g(); - bg_ = rgbMeans_.b() / rgbMeans_.g(); } - double computeColourError(const RGB &gains) const override + /* Minimum mean value below which AWB can't operate. */ + double minColourValue() const override { - /* - * Compute the sum of the squared colour error (non-greyness) as - * it appears in the log likelihood equation. - */ - double deltaR = gains.r() * rg_ - 1.0; - double deltaB = gains.b() * bg_ - 1.0; - double delta2 = deltaR * deltaR + deltaB * deltaB; - - return delta2; - } - - RGB rgbMeans() const override - { - return rgbMeans_; + return 2.0; } - -private: - RGB rgbMeans_; - double rg_; - double bg_; }; +/** + * \class Awb + * \brief Manage the white balance with automatic and manual controls + */ + Awb::Awb() : rgbMode_(false) { @@ -86,40 +56,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 +65,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, context.configuration.awb); /* * Define the measurement window for AWB as a centered rectangle @@ -148,64 +76,18 @@ int Awb::configure(IPAContext &context, context.configuration.awb.measureWindow.h_size = 3 * configInfo.outputSize.width / 4; context.configuration.awb.measureWindow.v_size = 3 * configInfo.outputSize.height / 4; - context.configuration.awb.enabled = true; - return 0; } /** * \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 +96,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,68 +165,27 @@ void Awb::process(IPAContext &context, const rkisp1_stat_buffer *stats, ControlList &metadata) { - IPAActiveState &activeState = context.activeState; + RkISP1AwbStats awbStats = calculateRgbMeans(frameContext, stats); - 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); + awbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats, + frameContext.lux.lux, metadata); +} +RkISP1AwbStats Awb::calculateRgbMeans(const IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats) const +{ if (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) { LOG(RkISP1Awb, Error) << "AWB data is missing in statistics"; - return; + return {}; } - const rkisp1_cif_isp_stat *params = &stats->params; - const rkisp1_cif_isp_awb_stat *awb = ¶ms->awb; + 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; + 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.max(1.0 / 256).min(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"; -} - -RGB Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const -{ Vector rgbMeans; if (rgbMode_) { @@ -419,7 +252,7 @@ RGB Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rk */ rgbMeans /= frameContext.awb.gains.max(0.01); - return rgbMeans; + return RkISP1AwbStats(rgbMeans); } REGISTER_IPA_ALGORITHM(Awb, "Awb") diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h index 60d9ef111495..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 e61391bb1510..81b1c7499706 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,16 @@ struct IPAHwSettings { bool compand; }; +struct RKISP1AwbSession : public ipa::awb::Session { + struct rkisp1_cif_isp_window measureWindow; +}; + 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 +102,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; @@ -174,11 +166,7 @@ struct IPAFrameContext : public FrameContext { bool autoGainModeChange; } agc; - struct { - RGB gains; - bool autoEnabled; - unsigned int temperatureK; - } awb; + ipa::awb::FrameContext awb; struct { BrightnessQ brightness; From patchwork Mon Jun 15 14:05: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: 26879 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 EF64BC324C for ; Mon, 15 Jun 2026 14:05:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 52144623E7; Mon, 15 Jun 2026 16:05:53 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="H53iUCxg"; 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 80508623E7 for ; Mon, 15 Jun 2026 16:05:47 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 95B0CAB4; Mon, 15 Jun 2026 16:05:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532314; bh=21E4tZLP2C1KoqRbobeEZiEboXquXxQviTk97QD8mew=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=H53iUCxgt0gOKw69NPhymcOzGgKHkYaQD30uHsDpwJbxOt1lProZ7H0G5Bco1nxkW x4ARjpPCDXyy6MR3ToV9ltClFJnBbHrchtYXP3gkluGLUMoLlgvMFNo2MuVMMhP/7A m30IUH2ri5kpEYVCeyYpdVj2MgKDAHDRIsX1HIyg= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:27 +0200 Subject: [PATCH 02/11] ipa: mali-c55: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-2-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=16990; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=21E4tZLP2C1KoqRbobeEZiEboXquXxQviTk97QD8mew=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa4LfItJ/ELlMDSF13DbjhPy3oY+qW6VL7VE ONYb4TSBNiJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuAAKCRByNAaPFqFW PE9zD/9VSxjC2jTlCwuTeI2piIHXH3oQby+njLbouVeZLErQcb0idrbRdSY7EYM/ksnqg0zMawe WC4PXZWpBr37TnPkhPL+bGfQ6M5YKDdVwRTQZ1YI8nhoL0O9DY6dGmHTvco/Fp3+K1xeKMlugem +Y8VImGOLtHd0JuMpY5/UtfNANNb89Kwk4C1rUuH/YvQCupOyfQj+mJMjA2EXvSduB13jC2g7Ig e1RqoV+wXAmnRnIesvTdkY2snzmNNrxIm7r8oxTJ06ouT96nr/mNL4V1JnPvUxJjuDiV9RSYo0g 4Ivqeh1s7aiktRf5zD8X+hJNg3dZpYW1FiVoXvRwG3RXPry2m4ik+rCn/g0iQzJ4KjDzxXiwCpf Bt3jqs5FUtA8kfwSbnrqvte8EhaXzyN5hg2vT+MBor5RyTD42z06LgHDoKzfiT0hMsFwvZcSYsX CAWlkN2mL3fwLUohe4qR2Yk5lDbrdy9nOaF44sJ8wO/qw5mtBBtMoPmgFEzW657FTumv/BR65Ge k9UidtarQLr+uHBXFrgjCvoO+QlMml9wAb/JXL9S8GATX9NNf/9OQuuZZX5iloxUYkWudhinQAj 4y0U1Q+/Pjr8pmUGA3z1dkmPcn0Ys7fuvDxZ+lBd4LAI5F5/sdX6fXBmQBxg58CGRW1qTupPlYD OdNmsHNDoHrGOig== 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 --- src/ipa/mali-c55/algorithms/awb.cpp | 198 ++++++++++++++++++------------------ src/ipa/mali-c55/algorithms/awb.h | 30 ++++-- src/ipa/mali-c55/ipa_context.cpp | 6 ++ src/ipa/mali-c55/ipa_context.h | 17 ++-- src/ipa/mali-c55/mali-c55.cpp | 27 +++-- 5 files changed, 152 insertions(+), 126 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/awb.cpp b/src/ipa/mali-c55/algorithms/awb.cpp index 8a671b52be59..97964f395325 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,63 +14,65 @@ #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) + : AwbStats(rgbMeans) + { + /* The Mali-C55 ISP already provides stats as R/G and B/G ratios. */ + rg_ = rgbMeans_.r(); + bg_ = rgbMeans_.b(); + } -Awb::Awb() + /* Minimum mean value below which AWB can't operate. */ + double minColourValue() const override + { + return 0.2; + } +}; + +/** + * \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, + context.configuration.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) +void Awb::configAwbMeas(MaliC55Params *params) { auto block = params->block(); @@ -117,56 +119,77 @@ 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; - fillConfigParamBlock(params); + configAwbMeas(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 +197,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..cc7ef583eafc 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); + void configAwbMeas(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..3d884ea17eb8 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 { @@ -29,6 +31,8 @@ struct IPASessionConfiguration { double maxAnalogueGain; } agc; + ipa::awb::Session awb; + struct { BayerFormat::Order bayerOrder; utils::Duration lineDuration; @@ -54,10 +58,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 +68,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 +83,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 Jun 15 14:05: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: 26880 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 0B585C324C for ; Mon, 15 Jun 2026 14:06:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5C1CE623FC; Mon, 15 Jun 2026 16:05:54 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BgRCbChG"; 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 AD592623E8 for ; Mon, 15 Jun 2026 16:05:47 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DD121C59; Mon, 15 Jun 2026 16:05:14 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532315; bh=ZiPclq2MeaNu27qrQtHqVVg0Naj67q9W+X5UXQ/0yl4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=BgRCbChGm7BjjTtDXCWmvSygvzKQd3WKBmUKlch68iQKOFLgDpPQFWLBKySNK/ZgL EFQ14LWua1+mpgLynhlMaWN8UcyD/682TZIpJXry5GHfFTvmnnQbFoVHrYP4wns3Cg zYSPH6qtz27smeXOEdhNpHyzWv3qo2omx7Ny/iXo= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:28 +0200 Subject: [PATCH 03/11] softisp: Split AWB from Combined Matrix MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-3-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=5890; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=sI2mHqGV/pTQpVaqsTnK9HOkhX5mykscCA5981d1dJk=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa4hoOSNrIAZTLDfnP3B7TVdinXeVKbnz+JO ukUG/ysgjKJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuAAKCRByNAaPFqFW PL7nD/4nl0nLbHWgIzdC6CFLw2MPP9V9qYqUeVchpWfXEfnmvdtpBjzEi+YrxYtTkrV11emsvm0 2H/ktklsGikHxE/XhzTJ27hacqv7ZS6/11dP7RRbuSKWkRX5UmiTRT37bvZFEEeFpDNu2FwF5v4 WLwUWsH9ckTwvdqO8NKxvZGUy7BKgV2d7eYJic9YIpdbjKpaGHoXO5nEE8EpRA1NOf2VQG7HmbH Cj7VJkBuY4m1mUEPC/4ZRe1NfdmIE0m+A8tfJRXceyI7zkkUjj+L6WTfmQ5U6lylBdjtrf2DTGQ EH4PaVi85IK8MPjAAfbX3oGywDFbdrbzMcWIQxqLDuYeeI7SU1QRYTAm3YUVuxvi/0ExmJ24x1A hZPhQHWPwVoRVjgeE4xFEJj9Xtqu11Lr9bgqjvuDMtVvg6SbDAuG01x3XDFgot69camWFA9t8rG 6CfumjXE5pcKfBIZaXomiBVzNRGDpWhF0tpdJFEG6TjF3YWyPmn7wPPXaSjVDLAdcqqKDiNJs1Z 7fb7JY77yALTPY9c2AT5VcQ5oYJsQdIKARkQjRSRz3v3Vdaig2ORTzCx76+0GotHYAcBAkDM7XI c+XwinbWSwhG6M5g5pkCb2ZLsHWfF+i3l52K5msTRs7UG5dNawxvwDx5MM342ZshMFIra1FIV8D Y32khVWsW/KytkA== 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 Move the AWB gains out of the combined matrix and pass them directly to the EGL shaders. Signed-off-by: Kieran Bingham Signed-off-by: Jacopo Mondi --- src/ipa/simple/algorithms/awb.cpp | 16 +++------------- src/ipa/simple/ipa_context.h | 5 +---- src/libcamera/shaders/bayer_1x_packed.frag | 4 ++++ src/libcamera/shaders/bayer_unpacked.frag | 4 ++++ src/libcamera/software_isp/debayer_egl.cpp | 5 +++++ src/libcamera/software_isp/debayer_egl.h | 3 +++ 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp index f5c88ea6f896..05155c83d172 100644 --- a/src/ipa/simple/algorithms/awb.cpp +++ b/src/ipa/simple/algorithms/awb.cpp @@ -38,15 +38,8 @@ void Awb::prepare(IPAContext &context, DebayerParams *params) { auto &gains = context.activeState.awb.gains; - Matrix gainMatrix = { { gains.r(), 0, 0, - 0, gains.g(), 0, - 0, 0, gains.b() } }; - context.activeState.combinedMatrix = - gainMatrix * context.activeState.combinedMatrix; - - frameContext.gains.red = gains.r(); - frameContext.gains.blue = gains.b(); + frameContext.gains = gains; params->gains = gains; } @@ -59,11 +52,8 @@ void Awb::process(IPAContext &context, const SwIspStats::Histogram &histogram = stats->yHistogram; const uint8_t blackLevel = context.activeState.blc.level; - const float mdGains[] = { - static_cast(frameContext.gains.red), - static_cast(frameContext.gains.blue) - }; - metadata.set(controls::ColourGains, mdGains); + metadata.set(controls::ColourGains, { frameContext.gains.r(), + frameContext.gains.b() }); if (!stats->valid) return; diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 34f7403a41d6..8ccfacb46a59 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -71,10 +71,7 @@ struct IPAFrameContext : public FrameContext { double gain; } sensor; - struct { - double red; - double blue; - } gains; + RGB gains; float gamma; std::optional contrast; diff --git a/src/libcamera/shaders/bayer_1x_packed.frag b/src/libcamera/shaders/bayer_1x_packed.frag index 23747f78a631..9a1992e219dd 100644 --- a/src/libcamera/shaders/bayer_1x_packed.frag +++ b/src/libcamera/shaders/bayer_1x_packed.frag @@ -65,6 +65,7 @@ uniform vec2 tex_step; uniform vec2 tex_bayer_first_red; uniform sampler2D tex_y; +uniform vec3 awb; uniform mat3 ccm; uniform vec3 blacklevel; uniform float gamma; @@ -227,6 +228,9 @@ void main(void) rgb = rgb - blacklevel; + /* Apply AWB gains, and saturate each channel at sensor range */ + rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0) - blacklevel); + /* * CCM is a 3x3 in the format * diff --git a/src/libcamera/shaders/bayer_unpacked.frag b/src/libcamera/shaders/bayer_unpacked.frag index 1b85196ae161..2def33c82c8e 100644 --- a/src/libcamera/shaders/bayer_unpacked.frag +++ b/src/libcamera/shaders/bayer_unpacked.frag @@ -24,6 +24,7 @@ uniform sampler2D tex_y; varying vec4 center; varying vec4 yCoord; varying vec4 xCoord; +uniform vec3 awb; uniform mat3 ccm; uniform vec3 blacklevel; uniform float gamma; @@ -130,6 +131,9 @@ void main(void) { rgb = rgb - blacklevel; + /* Apply AWB gains, and saturate each channel at sensor range */ + rgb = clamp(rgb * awb, vec3(0.0), vec3(1.0) - blacklevel); + /* * CCM is a 3x3 in the format * diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index fd8de3942aa3..227f698a1fac 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -115,6 +115,7 @@ int DebayerEGL::getShaderVariableLocations(void) attributeTexture_ = glGetAttribLocation(programId_, "textureIn"); textureUniformBayerDataIn_ = glGetUniformLocation(programId_, "tex_y"); + awbUniformDataIn_ = glGetUniformLocation(programId_, "awb"); ccmUniformDataIn_ = glGetUniformLocation(programId_, "ccm"); blackLevelUniformDataIn_ = glGetUniformLocation(programId_, "blacklevel"); gammaUniformDataIn_ = glGetUniformLocation(programId_, "gamma"); @@ -128,6 +129,7 @@ int DebayerEGL::getShaderVariableLocations(void) LOG(Debayer, Debug) << "vertexIn " << attributeVertex_ << " textureIn " << attributeTexture_ << " tex_y " << textureUniformBayerDataIn_ + << " awb " << awbUniformDataIn_ << " ccm " << ccmUniformDataIn_ << " blacklevel " << blackLevelUniformDataIn_ << " gamma " << gammaUniformDataIn_ @@ -502,6 +504,9 @@ void DebayerEGL::setShaderVariableValues(const DebayerParams ¶ms) glUniform3f(blackLevelUniformDataIn_, params.blackLevel[0], params.blackLevel[1], params.blackLevel[2]); LOG(Debayer, Debug) << " blackLevelUniformDataIn_ " << blackLevelUniformDataIn_ << " data " << params.blackLevel; + glUniform3f(awbUniformDataIn_, params.gains[0], params.gains[1], params.gains[2]); + LOG(Debayer, Debug) << " awbUniformDataIn_ " << awbUniformDataIn_ << " data " << params.gains; + /* * Gamma */ diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h index 943410fdd1fc..43038b45a109 100644 --- a/src/libcamera/software_isp/debayer_egl.h +++ b/src/libcamera/software_isp/debayer_egl.h @@ -90,6 +90,9 @@ private: GLint textureUniformBayerDataIn_; + /* Per-frame AWB gains */ + GLint awbUniformDataIn_; + /* Represent per-frame CCM as a uniform vector of floats 3 x 3 */ GLint ccmUniformDataIn_; From patchwork Mon Jun 15 14:05: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: 26881 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 A49CDC324C for ; Mon, 15 Jun 2026 14:06:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4299B623ED; Mon, 15 Jun 2026 16:05:55 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nODVaxyx"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 07B4B623CB for ; Mon, 15 Jun 2026 16:05:48 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3232E1D98; Mon, 15 Jun 2026 16:05:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532315; bh=R0z4AH0Gi3fmsi1UQyPGyIa89+mgUYL1fXQwatR4Xrk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=nODVaxyxpqn02qEJvYA7cylc0tQwyPuaqKuJ/gW7GAmOfF1lF+L9NzQgQxRyNl+j/ tOaVkSprLrf+MWNsVfjZXJmMkVCkUnnKCaCLQfXn1J6j8jbs7OKpxpNIkx6qYYJOrF 7IBNg+j4iRz2bI3gT6NMfQ635kfBFR8FD+zhnGOc= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:29 +0200 Subject: [PATCH 04/11] ipa: simple: awb: Port to use libipa AwbAlgorithm MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-4-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=10867; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=S2nsPBnxePsyFPIFSuEoDC87gIkYk7a6ISUmf8FnF9k=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa5Lr+g4EWLCIb3m2C2a36m0qWz0wiWvV4oW j3UZweGwryJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuQAKCRByNAaPFqFW PDT/D/0amVQw3/IG6Il0UnLzzsQRH+Qp+Ut8cvrD+OPBdFcX5NgxxtauqRq//ZvtHeLOPGruzSL LWyhP10I70cwr+UkFJJ/gIHhe97chEBnznhQWiTxfBO1YQQHH5HMk06kM25Te+myoAcNMTgOIYO WKd9yiTHIIZXtQ30V3bD2qhD/JAcmumIaxVfg/2cBN80iX/nmnXfVsEMiLyERLEH7MQHmrr5AnH CYzTFLcX6lA5HOjMqN8rHYS86qkOltNG3di1XEtmonQpSNP6/XsdLzkJK5MWHhJzDWELkS85E1V LKpmn4fzo1Gjh6z6Cj0LlMU04DYZzrDk4DHJcb3MW06K/3i2HoBeWU23e5DeLMscQu2fg4V6s9o VnXgeprHi3nTgeL6xToW8VsJ1kQKhfoxi6sbsJS8XL6qgAhG1V+ceu8jAydAyA09wPT7W34Y7Li QfWyQf2v1XBl3j84kWZZyhv9fUSxuK7wCGG/22iROrE/CRKZV8MOHq5ukBSiLhBhODOfTzj6YYo xv+P3SXskdW+N8Ou2ycQ91YCeJvmmxtvScDpPzL9ppiXJ6wKjo/dJisNGFXd1yS/hbPNRru3tOz amjK65zBNGVwiza/Z5VvDFNr8HpGWYLW4b5428PsyAvmOs4QDAvKmOHUd5fJnX3g1XaZg8Az9fz 2VEQMlLyjSJ8Qkg== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Kieran Bingham Port the SoftISP Awb algorithm to use the new libipa implementation of AwbAlgorithm. The awbAlgo_ class member is initialized with the Q<2, 8> type even if there is no physical register representation for SoftISP. Signed-off-by: Kieran Bingham Signed-off-by: Jacopo Mondi --- .../internal/software_isp/debayer_params.h | 2 +- src/ipa/simple/algorithms/awb.cpp | 112 ++++++++++++++------- src/ipa/simple/algorithms/awb.h | 29 ++++++ src/ipa/simple/algorithms/ccm.cpp | 2 +- src/ipa/simple/ipa_context.h | 14 +-- src/libcamera/software_isp/debayer_cpu.cpp | 2 +- 6 files changed, 117 insertions(+), 44 deletions(-) diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h index 6772b43bced4..b46bbd7a8273 100644 --- a/include/libcamera/internal/software_isp/debayer_params.h +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -24,7 +24,7 @@ struct DebayerParams { 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..01323d9779c5 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,79 @@ 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() {} + SimpleAwbStats(const RGB &rgbMeans) + : AwbStats(rgbMeans) + { + } + + /* Minimum mean value below which AWB can't operate. */ + double minColourValue() const override + { + return 0.2; + } +}; + +/** + * \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, + context.configuration.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 +104,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..cb36cd092e51 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, 15.999f] interval, which seems + * reasonable. + */ + AwbAlgorithm> awbAlgo_; }; } /* namespace ipa::soft::algorithms */ 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..0646f42d5618 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" @@ -25,6 +26,8 @@ namespace libcamera { namespace ipa::soft { struct IPASessionConfiguration { + ipa::awb::Session awb; + struct { int32_t exposureMin, exposureMax; double againMin, againMax, again10, againMinStep; @@ -36,6 +39,8 @@ struct IPASessionConfiguration { }; struct IPAActiveState { + ipa::awb::ActiveState awb; + struct { int32_t exposure; double again; @@ -48,11 +53,6 @@ struct IPAActiveState { double lastGain; } blc; - struct { - RGB gains; - unsigned int temperatureK; - } awb; - Matrix combinedMatrix; struct { @@ -64,6 +64,8 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { + ipa::awb::FrameContext awb; + Matrix ccm; struct { @@ -71,8 +73,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 d2596d32bbcd..fc171f874833 100644 --- a/src/libcamera/software_isp/debayer_cpu.cpp +++ b/src/libcamera/software_isp/debayer_cpu.cpp @@ -1051,7 +1051,7 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms) auto &blue = swapRedBlueGains_ ? red_ : blue_; for (unsigned int i = 0; i < kRGBLookupSize; i++) { /* Apply gamma after gain! */ - const RGB lutGains = (gains * i / div).min(gammaTableSize - 1); + const RGB lutGains = (gains * i / div).min(gammaTableSize - 1); red[i] = gammaTable_[static_cast(lutGains.r())]; green[i] = gammaTable_[static_cast(lutGains.g())]; blue[i] = gammaTable_[static_cast(lutGains.b())]; From patchwork Mon Jun 15 14:05:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 26882 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id E4E3EC324C for ; Mon, 15 Jun 2026 14:06:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 379D4623F4; Mon, 15 Jun 2026 16:05:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="IYZucAFK"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 42789623E1 for ; Mon, 15 Jun 2026 16:05:48 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7B94B1E1B; Mon, 15 Jun 2026 16:05:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532315; bh=Ll5r+4v/REyWSytwnhKQKhR6P2gts8C2571acQh/v1s=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=IYZucAFKDnPBrP+Dp2b2kBXUNy/1HWW4ebY3XHmYOa18sQkinxPbPXIfOBy32jNGj 5jkRbURQb0cIwEqWb7J67sRDSAkNeCa9n+rByoNvYbj5GqI02UYXw9y+jPnuWfzn4Y YAMCE+duGyzPd+YUZt4sXGmb8H1gzlXJCAX44/fE= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:30 +0200 Subject: [PATCH 05/11] ipa: libipa: Add CcmAlgorithm to libipa MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-5-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=12961; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=Ll5r+4v/REyWSytwnhKQKhR6P2gts8C2571acQh/v1s=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa5VeUv7idtLyoxzUuIZU6RUOwHU14UvL7zw Xu+Xd45vrOJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuQAKCRByNAaPFqFW PHeGD/9c3/rI2F8j2PKZa9ohpirm6zJuugWz09Y0Z0x4G+UHv1vknl5QN18KLZVtBRnaG+QZFjW dXpM7XWEnFOHNeFWyQYLLVW6s03ONkTXhEglH2f+dyg6i/3p1lftCME8PQVRoALp6+/hAFXouVr Tq5gn1EUUWIbTG7ghtg4DAFwljvQ9LGPopAeluIakyDmwEoVjjUziyhk1Fu5RjlZ7Lwi9qOmk8n m8zxBKBSKEGzFMpL5RRANnVkc73IckKYTJhfjwya3KD8Tdrg/PHcBLIe2YAPfHmGLz9QBP6nYrH S1h2XBg6enCLQKBj/CDrnlLI6wgxib2clUhNGUmeMSy3zh7QlT9YFz9MxWjWOd16AaujJQXSlNh +kQSbEvQCkbDc9+3K4Ys8jHHuE6ketAWlkWZoRFxXjrOtNemD8pQgHcuuyNJ6NG+k1FpHBoVeAN 9N68sPmkqRSjgCVtUTXg4UwJMTlvOMRV7KIs5LMtfAbgAlyxG0XRWVauKEmNXDG+A02NW1JNhVc HenGAQeFx7VuO7H2r7x7Sm5vlSaVpHH8XAca3L3Vy6qios0ytqK5Yp1hZUl8M+dyMtx2IxckZzo F/rlBX2I1ndq9L6TIBb37lApfE4uncP8C+VTnr4aTVtsbHBDgwukZfb++OhuRyCN2f8RHX5kAB8 0yKGrAcFT/j9mWQ== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a CcmAlgorithm to libipa. The CcmAlgorithm performs interpolation of the colour correction matrices as loaded from tuning file on a colour temperature. The implementation is based on the existing RkISP1 CCM algorihtm. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/ccm.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/ccm.h | 87 +++++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 362 insertions(+) diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp new file mode 100644 index 000000000000..278f8534e899 --- /dev/null +++ b/src/ipa/libipa/ccm.cpp @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA CCM algorithm + */ + +#include "ccm.h" + +/** + * \file ccm.h + * \brief libipa ccm (Colour Correction Matrix) algorithm + */ + +namespace libcamera { + +namespace ipa { + +LOG_DEFINE_CATEGORY(Ccm) + +namespace ccm { + +/** + * \struct ActiveState + * \brief Active ccm state + * + * \var ActiveState::manual + * \brief The most recent manually requested ccm state + * + * \var ActiveState::automatic + * \brief The most recent automatically calculated ccm state + */ + +/** + * \struct ActiveState::CcmState + * \brief Ccm coefficients and offsets + * + * \var ActiveState::CcmState::ccm + * \brief Matrix of 3x3 ccm coefficients + * + * \var ActiveState::CcmState::offsets + * \brief Vector of RGB ccm offsets + */ + +/** + * \struct FrameContext + * \brief Per-frame ccm state + * + * \var FrameContext::ccm + * \brief Matrix of 3x3 ccm coefficients + * + * \var FrameContext::offsets + * \brief Vector of RGB ccm offsets + */ + +} /* namespace ccm */ + +/** + * \class CcmAlgorithmBase + * \brief Base class for CcmAlgorithm for non-templated functions implementation + * + * Base class for CcmAlgorithm where non-templated functions are implemented. + * IPA implementations shall use CcmAlgorithm and not this class. + */ + +/** + * \brief Initialize the algorithm with the given tuning data + * \param[in] tuningData The tuning data to use for the algorithm + * + * Parse \a tuningData to initialize the ccm algorithm and register controls. + * IPA modules are expected to call this function as part of their + * implementation of Algorithm::init(). + * + * \return 0 on success, a negative error code otherwise + */ +int CcmAlgorithmBase::init(const ValueNode &tuningData) +{ + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); + if (ret < 0) { + LOG(Ccm, Warning) + << "Failed to parse 'ccm' " + << "parameter from tuning file; falling back to unit matrix"; + ccm_.setData({ { 0, Matrix::identity() } }); + } + + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); + if (ret < 0) { + LOG(Ccm, Warning) + << "Failed to parse 'offsets' " + << "parameter from tuning file; falling back to zero offsets"; + + offsets_.setData({ { 0, Matrix({ 0, 0, 0 }) } }); + } + + return 0; +} + +/** + * \brief Configure the ccm algorithm + * \param[in] state The ccm active state + * \param[in] temperatureK The colour temperature in Kelvin + * + * Configure the ccm algorithm by initializing the manual and automatic + * states in \a state by interpolating the default colour correction matrix + * with the given colour temperature \a temperatureK. + * + * \return 0 if successful, an error code otherwise + */ +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) +{ + state.manual.ccm = ccm_.getInterpolated(temperatureK); + state.manual.offsets = offsets_.getInterpolated(temperatureK); + state.automatic.ccm = ccm_.getInterpolated(temperatureK); + state.automatic.offsets = offsets_.getInterpolated(temperatureK); + + return 0; +} + +/** + * \brief Queue a Request to the ccm algorithm + * \param[in] state The ccm active state + * \param[in] context The ccm frame context + * \param[in] controls The list of controls part of the Request + * + * Queue a new Request to the ccm algorithm and store the manual colour + * correction matrix and temperature in \a frameContext. + * + * The currently handled controls are: + * - controls::ColourTemperature + * - controls::ColourCorrectionMatrix + * + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is + * stored in \a state and \a context. + * + * When controls::ColourTemperature is passed in, the matrices loaded from + * configuration file are interpolated with the give temperature and the result + * is stored in \a state and \a context. + * + * If the IPA is running in manual mode, the IPA ccm algorithm implementations + * can use the matrix coefficients and offsets directly from \a context after + * calling this function to program the HW ccm engine, without calling prepare(). + */ +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, + ccm::FrameContext &context, + const ControlList &controls) +{ + const auto &colourTemperature = controls.get(controls::ColourTemperature); + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); + if (ccmMatrix) { + state.manual.ccm = Matrix(*ccmMatrix); + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " + << state.manual.ccm; + } else if (colourTemperature) { + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " + << state.manual.ccm; + } + + context.ccm = state.manual.ccm; + context.offsets = state.manual.offsets; +} + +/** + * \brief Calculate the matrix coefficients for a colour temperature + * \param[in] state The ccm active state + * \param[in] context The ccm frame context + * \param[in] frame The frame number + * \param[in] temperatureK The colour temperature in Kelvin + * + * Interpolate the colour correction matrices as loaded from configuration file + * for colour temperature \a temperatureK. + * + * The function shall only be called if the IPA algorithm is running in auto + * mode. If running in manual mode the application supplied correction matrix is + * stored in \a frameContext at queueRequest() time. + */ +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, + ccm::FrameContext &context, + unsigned int frame, unsigned int temperatureK) +{ + if (frame > 0 && temperatureK == ct_) { + context.ccm = state.automatic.ccm; + return; + } + + ct_ = temperatureK; + context.ccm = ccm_.getInterpolated(ct_); + context.offsets = offsets_.getInterpolated(ct_); + + state.automatic.ccm = context.ccm; + state.automatic.offsets = context.offsets; +} + +/** + * \brief Populate metadata with the latest correction matrix coefficients + * \param[in] context The ccm frame context + * \param[out] metadata The metadata list + */ +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) +{ + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); +} + +/** + * \var CcmAlgorithmBase::coeffMin_ + * \brief The minimum supported coefficients value + * + * Minimum coefficient value used to clamp the ccm algorithm calculation results + * in the range supported by the platform ccm engine. + * + * The min and max gain values are initialized by CcmAlgorithm::init(). + */ + +/** + * \var CcmAlgorithmBase::coeffMax_ + * \brief The maximum supported coefficients value + * + * Maximum coefficient value used to clamp the ccm algorithm calculation results + * in the range supported by the platform ccm engine. + * + * The min and max gain values are initialized by CcmAlgorithm::init(). + */ + +/** + * \class CcmAlgorithm + * \brief The libipa ccm algorithm + * \tparam Q The fixedpoint register representation of the colour correction + * coefficients + * + * Implement the ccm algorithm for libipa. + * + * The CcmAlgorithm class implement an interface similar in spirit to the one + * of the Algorithm class. IPA modules are expected to store an instance of + * CcmAlgorithm as class member, template it with the ccm coefficients register + * representation and call its function in their implementations of the + * Algorithm interface. + * + * The CcmAlgorithm class provides an init() function where tuning data are + * parsed and the per-colour temperature correction matrices are loaded from + * the tuning file. + * + * CcmAlgorithm supports both automatic and manual colour correction operations, + * but doesn't offer a way to select one of them. Enabling or disabling + * automatic ccm operations usually goes through the Awb algorithm + * enable/disable as the two algorithms should work with the same mode. + * + * When the Awb algorithm runs in manual mode a custom colour correction matrix + * or a custom colour temperature can be supplied to the ccm algorithm at + * queueRequest() time. If the Request contains a color correction matrix + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in + * the FrameContext and the IPA module can immediately use them and doesn't need + * to call process(). If a custom colour temperature is provided + * (controls::ColourTemperature) then the matrices loaded from configuration are + * interpolated with it and the result is saved in the FrameContext. In this + * case as well IPA modules can use the result immediately and should avoid + * calling process(). + * + * When the Awb algorithm runs in automatic mode instead, it estimates the scene + * colour temperature. The estimated colour temperature shall be passed to + * process(), where it is used to interpolate the matrices loaded from the + * tuning file. The resulting coefficients are stored in the FrameContext for + * the IPA algorithm to use them to program their ccm engine registers. + */ + +/** + * \fn CcmAlgorithm::init() + * \param[in] controls The info map of the IPA controls + * \copydoc CcmAlgorithmBase::init() + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h new file mode 100644 index 000000000000..26ea0789481d --- /dev/null +++ b/src/ipa/libipa/ccm.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA CCM algorithm + */ + +#pragma once + +#include +#include + +#include "libcamera/internal/matrix.h" + +#include "fixedpoint.h" +#include "interpolator.h" + +namespace libcamera { + +namespace ipa { + +namespace ccm { + +struct ActiveState { + struct CcmState { + Matrix ccm; + Matrix offsets; + }; + + struct CcmState manual; + struct CcmState automatic; +}; + +struct FrameContext { + Matrix ccm; + Matrix offsets; +}; + +} /* namespace ccm */ + +class CcmAlgorithmBase +{ +public: + int init(const ValueNode &tuningData); + int configure(ccm::ActiveState &state, unsigned int temperatureK); + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, + const ControlList &controls); + + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, + unsigned int frame, unsigned int temperatureK); + void process(ccm::FrameContext &context, ControlList &metadata); + +protected: + float coeffMin_; + float coeffMax_; + +private: + unsigned int ct_; + Interpolator> ccm_; + Interpolator> offsets_; +}; + +template +class CcmAlgorithm : public CcmAlgorithmBase +{ +public: + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) + { + int ret = CcmAlgorithmBase::init(tuningData); + if (ret) + return ret; + + coeffMin_ = Q::TraitsType::min; + coeffMax_ = Q::TraitsType::max; + + controls[&controls::ColourCorrectionMatrix] = + ControlInfo(ControlValue(coeffMin_), + ControlValue(coeffMax_), + ControlValue(Matrix::identity().data())); + + return 0; + } +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 963c5ee73063..edf8eabd8b78 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -7,6 +7,7 @@ libipa_headers = files([ 'awb_grey.h', 'awb.h', 'camera_sensor_helper.h', + 'ccm.h', 'colours.h', 'exposure_mode_helper.h', 'fc_queue.h', @@ -28,6 +29,7 @@ libipa_sources = files([ 'awb_grey.cpp', 'awb.cpp', 'camera_sensor_helper.cpp', + 'ccm.cpp', 'colours.cpp', 'exposure_mode_helper.cpp', 'fc_queue.cpp', From patchwork Mon Jun 15 14:05: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: 26883 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 14441C32D4 for ; Mon, 15 Jun 2026 14:06:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 66A8D623E3; Mon, 15 Jun 2026 16:05:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="H3tPig+E"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 88028623E5 for ; Mon, 15 Jun 2026 16:05:48 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BB1051E2B; Mon, 15 Jun 2026 16:05:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532315; bh=iCLTTGrtQhxDI3gU4TkO4My19UTY9C00k1cA68SGo7Y=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=H3tPig+EqMNk4XXt2ayFtnzTw4wljlpDU2hwaBzVFuNzN6nJ+Vcx51PbTsAQoa/tQ plaFGvuW/j9BrQTc9Kmi2NBqI3vsfKEncFDzFyDxosFKMsVLbVgqQp3Bk41HPwpZVT 5xaaRwijhsTCinZxj7UGDsmorOn2Ls8/lljQ6GCU= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:31 +0200 Subject: [PATCH 06/11] ipa: rkisp1: ccm: Port to use CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-6-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=7981; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=iCLTTGrtQhxDI3gU4TkO4My19UTY9C00k1cA68SGo7Y=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa5X0F9upC4yG9eNASUby0DlOnLC6xyicCwZ mm8Pl9NkDmJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuQAKCRByNAaPFqFW PLJvD/4oVuFWm5KRWqQ5a7LPfRdhuf6gsaGzPd5MeCEIrma2JF3tWhR23lDC/OpC9E850nCZOw0 6qvOZ8F/lMw02QTIq+gJYidbQGAjQ+q+fAQPVXJDSZp8De1eYSjoBI1m6tKg6alzE7CPb/w0NAq f9fBBhQ0o+X9lPfjRBrm6TONbh+1qVrX9YKme6pHQCCBzpTAVO5t+i9luwSSgwD8gv4rilvVjJi UQSPZzfCtKJQDgi+lgbNa93nC3zcALQhcRO7ugU0RUUQrjD3TfNTlISF5Nsea+Rqr5zYlmoLSjM SEWVTEQsV8fNeHdsT0ViA9325pPzyuyIFnZVYcXJB60Em4JFyOqd4atigUAssZ1vOpgtVtaWxfI rn/BP4YBZrrzPROyvG7H6L/gWmnD11IkD9qxmOWhqm3OtASg4E97tHW+Ym7vQm2o/FaD0fAcWRC H1JXHbuAs6NXCJKBFTylafvpBNu+s2J9S+XBmfY8mKF6CH+vNNnFQs240bG9yfd8SXMlZHjFX0D 9LP3BT9X/px3rbIB6d3WiSUHHLwNZDVF+zf8euatevikC3NSczE9oO6G0J5tlKy/uZzqH7AHOz0 RYNmYRXrPMQMCc8aPQRnx7dsJOGvPUGhIB4BDGdiah74sIyYJhWeHBuMlBIJklCzepOLG/7zgS+ 6cLTh0J4J3zHSDw== 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 --- src/ipa/rkisp1/algorithms/ccm.cpp | 97 ++++++++------------------------------- src/ipa/rkisp1/algorithms/ccm.h | 20 ++++---- src/ipa/rkisp1/ipa_context.h | 10 ++-- 3 files changed, 33 insertions(+), 94 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 9ac537426d16..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,14 +46,9 @@ public: ControlList &metadata) override; private: - void parseYaml(const ValueNode &tuningData); - void setParameters(struct rkisp1_cif_isp_ctk_config &config, - const Matrix &matrix, - const Matrix &offsets); - - unsigned int ct_; - Interpolator> ccm_; - Interpolator> offsets_; + void setParameters(RkISP1Params *params, IPAFrameContext &context); + + CcmAlgorithm> ccmAlgo_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 81b1c7499706..cd97e10bcf2b 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" @@ -104,10 +105,7 @@ struct IPAActiveState { ipa::awb::ActiveState awb; - struct { - Matrix manual; - Matrix automatic; - } ccm; + ipa::ccm::ActiveState ccm; struct { BrightnessQ brightness; @@ -203,9 +201,7 @@ struct IPAFrameContext : public FrameContext { double gain; } sensor; - struct { - Matrix ccm; - } ccm; + ipa::ccm::FrameContext ccm; struct { double lux; From patchwork Mon Jun 15 14:05: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: 26884 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 047F9C324C for ; Mon, 15 Jun 2026 14:06:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CDCC1623FF; Mon, 15 Jun 2026 16:05:59 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="VRyIxMGG"; 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 C9061623E9 for ; Mon, 15 Jun 2026 16:05:48 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 05EFEAB4; Mon, 15 Jun 2026 16:05:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532316; bh=CO1uyBJUO35HKWOabimLdIvmHaBVEqqV61H91F6gH+Y=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=VRyIxMGGqJdkT+wdpiAj41T3tsKrwNfn517I2OVpTrXxghGhaSIjN8CQF8drqVdTh phxgnGflo6ubgEUUQdp8YZv359l1ms9wkR7EFwWDCbnD4o+ECqViYp0+17Dasd29T8 sxgRZuMePEbfQmlhjLGPdTQV+NosNqCQd6etCemc= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:32 +0200 Subject: [PATCH 07/11] ipa: simple: Use libipa CcmAlgorithm MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-7-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=5853; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=lh02C6rkWqTUA9wW6cSugRorHYlriBx9qqmGyTJ+Zhk=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa5SqsbG+pZoDJjiUTQz/WS5HLJ4G5uDXDmK TcjS74X7ymJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuQAKCRByNAaPFqFW PAePD/98DE4RiGQx8gBNwxWq8D3nMav0N1GFAPe5U9BrTJ93Kjpuh+Lw7LlVRT1LloZL+R8HLTf McMBtOs/3415pG0KuLIstLDqd++fgFWyaC2EXdCKb9tkjOq41gD+zwiSzDV6eZUMXqc0S92WL21 dfQZCt5Ik0GZIecTbpk9ubeTVjhchcxkQtOYRffQpXXrC0vd1Tp5IgQ+BK9qPO5GW9zJTv7//Wa GPx7ylPlAV+6iftS7tjpRkUr323Q6zVJficElPPOayIbp7l3EFOOePH9F3xUX+tvbefJUWEYIEy MeV8t07oAXfK2NywntbyZKPLdM5xd3wUV5v3/l7sVgujrCGLDIkoYVIzIsaLCAm7JrBz+2FDL3U JCNivfIKkNmgniBnkCiVpGUu5tH83sWVJSmivWalrJoNkqXBqaJ0tRSfjcRbkkANEvT4/Y8Ypcf UTP65A4DXbxNHag7Q61Ug3VAUZqAkUL2OqwP2p3seud8EDrRbgz8QaGzFd/g2uIVyUUb5535JXB 7h57TkWz39iqjH02xXmmmPmjhkoNf5neEyWdWBr8b/EvYw9+LaTl/fhg/0NMcnqBqhc8QPULIkc QtpXkeDnjRjcmrgoSwcOXTeb5E/TkmQI4xIvh6Dkf9JfNL/twdJ7eWkxe/6sCI2vXBjsa7ZAPv1 01zOl+yc4mB/GOA== 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 | 69 +++++++++++++++++++++------------------ src/ipa/simple/algorithms/ccm.h | 23 ++++++++----- src/ipa/simple/ipa_context.h | 5 +-- 3 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp index ff37c718c6e4..6cf55bd93db0 100644 --- a/src/ipa/simple/algorithms/ccm.cpp +++ b/src/ipa/simple/algorithms/ccm.cpp @@ -8,54 +8,61 @@ #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; - } + return ccmAlgo_.init(tuningData, context.ctrlMap); +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Ccm::configure(IPAContext &context, + [[maybe_unused]] const IPAConfigInfo &configInfo) +{ + return ccmAlgo_.configure(context.activeState.ccm, + context.activeState.awb.automatic.temperatureK); +} - context.ccmEnabled = true; +/** + * \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; - return 0; + 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) { - const unsigned int ct = frameContext.awb.temperatureK; - - /* Change CCM only on bigger temperature changes. */ - if (!currentCcm_ || - utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) { - currentCcm_ = ccm_.getInterpolated(ct); - lastCt_ = ct; - } - + 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 +71,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 0646f42d5618..1b40591e39cb 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" @@ -40,6 +41,7 @@ struct IPASessionConfiguration { struct IPAActiveState { ipa::awb::ActiveState awb; + ipa::ccm::ActiveState ccm; struct { int32_t exposure; @@ -65,8 +67,7 @@ struct IPAActiveState { struct IPAFrameContext : public FrameContext { ipa::awb::FrameContext awb; - - Matrix ccm; + ipa::ccm::FrameContext ccm; struct { int32_t exposure; From patchwork Mon Jun 15 14:05: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: 26885 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 ABD92C32D4 for ; Mon, 15 Jun 2026 14:06:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5C133623F1; Mon, 15 Jun 2026 16:06:01 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="uRIXCLuD"; 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 187EF623EB for ; Mon, 15 Jun 2026 16:05:49 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4E93B1E48; Mon, 15 Jun 2026 16:05:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532316; bh=6bqc2/XcD2qmj+9SAp2g4fk3X/yoP9cnFEchCTiEYUo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=uRIXCLuD5TeV1+Ht7PoY/b6NpW9xAOTbUpoCIl7R2nzDbt3rRrxs6dLoHggT5ai5f C/MifBgHBn6zBQ4aQxCG3INlgBXEHNPsl0fOSYwBLeMZss8D2Ai8RoAu98nXwtl9SJ rYEKziLSlnRTvp7MbRqdR+Z6hiE2RyaRq7zpQcjs= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:33 +0200 Subject: [PATCH 08/11] ipa: mali-c55: Implement Ccm algorithm MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-8-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=9110; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=6bqc2/XcD2qmj+9SAp2g4fk3X/yoP9cnFEchCTiEYUo=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa51zvayb9KaJ77YW3k6C3D6StTZBnxiWg7l 17aHHcep7yJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuQAKCRByNAaPFqFW PELeD/9k3vWVtL6j3UBKlORsjdZWL9hU+WqKFEHuFASyrjx6dvW0Du8V5EjUWPF+oNMhcKxxUbL FRIYvMRkQUXxjKNjTDKUXg6Vd1XHJMLy3l4R6dwtarcN5wFn1/6uWrrZehJeFTQr8Fh6FLd3lhR oKgBOo4seibw4itU6EFs+PgWmDEuL/kfjCGLPsWITvwZWHy4Bzj9LMqv2rf8pKIcGiozovqdNkF s3rdW5GBFupI+bESgJ9RQhqJdD63a9TeLb/Fn07JexqZC7Nps1lLa2RhnlnI4RtzqsVUgSCnB6E EDzrxIZnyFRjV/+Yuqws/WVLcPp1FBm0Azy1eX2WtLTqNKGSZONrJf+ehJOOQf0ueC/DTnYzQqX ce61uGAbRPoIRmAhoyoSDL4F+L5tnPoJK7YjwMUvqzRg52MlbPXSmy2OmgSp2OyNEYODdaTiCEM LEKxaigHVXeBdhotfxBSYq5gwYshMevu3v4FjaMrO2rf95lDwL/kHxbt+hjWMkZiNebJStu1O/8 /KF6ruVFSWxANhwVpiPKBQgoCblvvCwgcQDdfYqRHPspJKzd954zur69Gvt/1A2n+l9qJQYRjfT ThqdrYF2SQyDHaQsDm9yIIrSPAGr6HngPjHpB55jQV5E9ruSOlF+fRQxiM2I8BD7EWdz7WOT+UY dE29+68tj01lh7g== 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 | 175 ++++++++++++++++++++++++++++++++ src/ipa/mali-c55/algorithms/ccm.h | 66 ++++++++++++ src/ipa/mali-c55/algorithms/meson.build | 1 + src/ipa/mali-c55/ipa_context.h | 5 + 4 files changed, 247 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..008ab2d1e31e --- /dev/null +++ b/src/ipa/mali-c55/algorithms/ccm.cpp @@ -0,0 +1,175 @@ +/* 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(); + + if (val && ccm[i][j] < 0) { + /* + * 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 + */ + val = ((~(val & ~(1 << 12)) & 0x1ff) + 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 3d884ea17eb8..2d3e91d56baa 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 { @@ -59,6 +60,8 @@ struct IPAActiveState { } agc; ipa::awb::ActiveState awb; + + ipa::ccm::ActiveState ccm; }; struct IPAFrameContext : public FrameContext { @@ -69,6 +72,8 @@ struct IPAFrameContext : public FrameContext { } agc; ipa::awb::FrameContext awb; + + ipa::ccm::FrameContext ccm; }; struct IPAContext { From patchwork Mon Jun 15 14:05: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: 26886 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 9928EC3301 for ; Mon, 15 Jun 2026 14:06:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D5EB7623FA; Mon, 15 Jun 2026 16:06:02 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="PE92C4UM"; 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 65DF4623EC for ; Mon, 15 Jun 2026 16:05:49 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 92D60241; Mon, 15 Jun 2026 16:05:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532316; bh=il6dW1yhdjqzh7pzp9AACFqt7c/EgOicybB1lNxWM78=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=PE92C4UMpQ4vlJfJfj4Jz+jVxJnxDbkuIezpyvcX9SD3kphOiWPrEYEvSneEhD8H1 HyICdpCeVthVXIxqeUbaC+uYJaf59XNTUTaFO8nd0baBp1cN6aJFO0KBcHpi4dRcC6 +NdNm/nAdAEH4Xk4H9SVtC+jr5dLjKZFslTAnwig= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:34 +0200 Subject: [PATCH 09/11] ipa: libipa: Introduce LscAlgorithm MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-9-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=58176; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=il6dW1yhdjqzh7pzp9AACFqt7c/EgOicybB1lNxWM78=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa5vp8qTM83epZioQu7EdJVoGDXEUalj+W3k DcHd68U2HaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGuQAKCRByNAaPFqFW PDwJD/kBwzJ5KLtK/UtgWf4FD9/1ubii5IFd2pugmaLFoVRC8HSCb1V3gLR/xYED7odZVVrc+Nv zLtKtR9M9nnwN448By7xGAGY0AmPiRcwS1kgd/afyib9QZiIGtct6VmWZJ2QOI5aJF3F3rrfx5w lOgj6erZLGQoWtkmtyIolOiTgwb6bUua3IVSueU5LuhnXtS0IWV6M0xLnQbN752sQm1+FqxqpGS hWmkUlJzjrA6UStXeCPQbrxyIUoWfqo8Bi+m9QlAPR1P+Y4X0InB1n/S96USzxIBYS4ATGIRmuG H2/5R8daXs9guSjk+IVNIvqreBfy7xvQ9ljoFhokISU2NTLhU/xLrTHOG+glFInJtdnijZmyNT0 bLqecvE96/GNjf5jul/0V9fKyyr97mrrnD5UZFshy2rCMoUNZLcDUn38WD0azpv/7Vr1FB63msJ O8Y23R27KKelHPjxC5ms9W+YoVgBclVnEn3URJL/LUh28EUcxHjwhU3/3ynRPLG0vv8dX7L2Qtp 0C4Ne5Qlx3oVZp/3oiWx0qqQdaRsFeKsYiA8pDv6D+m4+G99DMuoGSAAiAmbWTo462iJA/AwhyZ UrvCN1Bi6pe/rAe8VefC0PuYAR3//+/2L60LcxRpJB316YgILdzfoUQQ9zw8mPENW0KeQemJn3j w0CKJD+18NdWGtg== 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 an Lsc algorithm implementation in libIPA. The LscAlgorithm class supports two algorithms: grid-based lsc and polynomial-based lsc. Both implementations have been generalized from the existing ones in the RkISP1 IPA. Signed-off-by: Jacopo Mondi --- src/ipa/libipa/lsc.cpp | 322 +++++++++++++++++++++++++++++ src/ipa/libipa/lsc.h | 145 +++++++++++++ src/ipa/libipa/lsc_base.cpp | 152 ++++++++++++++ src/ipa/libipa/lsc_base.h | 59 ++++++ src/ipa/libipa/lsc_grid.cpp | 33 +++ src/ipa/libipa/lsc_grid.h | 113 ++++++++++ src/ipa/libipa/lsc_polynomial.cpp | 133 +++++++++++- src/ipa/libipa/lsc_polynomial.h | 135 ++++++++---- src/ipa/libipa/meson.build | 5 + src/ipa/rkisp1/algorithms/lsc.cpp | 424 +++++--------------------------------- src/ipa/rkisp1/algorithms/lsc.h | 45 ++-- src/ipa/rkisp1/ipa_context.h | 10 +- 12 files changed, 1126 insertions(+), 450 deletions(-) diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp new file mode 100644 index 000000000000..ae713a776778 --- /dev/null +++ b/src/ipa/libipa/lsc.cpp @@ -0,0 +1,322 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithms + */ + +#include "lsc.h" + +#include + +/** + * \file lsc.h + * \brief libipa lsc algorithm + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Lsc) + +namespace ipa { + +namespace lsc { + +/** + * \struct ActiveState + * \brief The lsc active state + * + * \var ActiveState::enabled + * \brief Boolean flag for the LscAlgorithm enable status + */ + +/** + * \struct FrameContext + * \brief The lsc frame context + * + * \var FrameContext::enabled + * \brief Boolean flag for the LscAlgorithm enable status + * + * \var FrameContext::update + * \brief Boolean flag for the LscAlgorithm updated status + */ + +} /* namespace lsc */ + +#ifndef __DOXYGEN__ +template<> +void Interpolator>:: + interpolate(const lsc::Components &a, + const lsc::Components &b, + lsc::Components &dest, + double lambda) +{ + for (auto const &[k, v] : a) + interpolateVector(v, b.at(k), dest[k], lambda); +} +#endif + +/** + * \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 T The type used to store the gain values when loaded from tuning file + * \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 + * multipliers to all pixels to compensate for the lens shading effect. + * + * The LscAlgorithm implements the libipa Lens Shading Correction algorithm. + * It provides support for parsing the tuning file content and generate 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 (LscGrid) or as radial polynomial (LscPolynomial). The gain + * 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 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 + * + * 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 + * applied to any sensor configuration. Grid-based lsc tables cannot be + * re-sampled and the configuration as parsed from the tuning file is used for + * all sensor configurations providing best-effort results. + * + * When the IPA algorithms wants to get access to the (resampled) 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(). + */ + +/** + * \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 + * + * 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 + */ + +/** + * \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 + * \param[in] yPos List of vertical positions of the LSC grid nodes + * + * Re-sample the lsc data for an \a analogCrop. + * + * Lsc data 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 only supported by polynomial-based lsc tables. + * + * \sa LscImplementation::resampleLscData + * + * \return 0 on success, a negative error code otherwise + */ + +/** + * \fn LscAlgorithm::interpolateComponents + * \brief Interpolate the lsc tables for a given colour temperature + * \param[in] ct The colour temperature + * + * Lsc data 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 new file mode 100644 index 000000000000..8bfa3a7c4ecf --- /dev/null +++ b/src/ipa/libipa/lsc.h @@ -0,0 +1,145 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Lsc algorithms + */ + +#pragma once + +#include + +#include + +#include "interpolator.h" +#include "lsc_grid.h" +#include "lsc_polynomial.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Lsc) + +namespace ipa { + +namespace lsc { + +struct ActiveState { + bool enabled; +}; + +struct FrameContext { + bool enabled; + bool update; +}; + +} /* 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 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, + LscDescriptor descriptor) + { + polynomial_ = false; + + std::string type = tuningData["type"].get("table"); + if (type == "table") { + impl_ = std::make_unique>(); + LOG(Lsc, Debug) << "Using grid-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) + { + LOG(Lsc, Debug) << "Sample Lsc data for " << analogCrop; + lsc::ComponentsMap lscData = + impl_->resampleLscData(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) + { + return sets_.getInterpolated(ct); + } + + const lsc::ComponentsMap getComponents() + { + return lscData_; + } + +private: + std::unique_ptr> impl_; + Interpolator> sets_; + lsc::ComponentsMap lscData_; + bool polynomial_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp new file mode 100644 index 000000000000..fba8d2f00d54 --- /dev/null +++ b/src/ipa/libipa/lsc_base.cpp @@ -0,0 +1,152 @@ +/* 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 + * + * Split the common classes and types definitions to a dedicated file to avoid + * circular inclusions. + * + * Both LscGrid 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_grid.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_grid.h and lsc_polynomial.h, which are then included + * by lsc.h. + */ + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +/** + * \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 of type \a T. + * + * 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. + */ + +/** + * \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 + * several different colour temperatures. + * + * This class associates an lsc table to the colour temperature used when the + * table has been generated. + */ + +} /* namespace lsc */ + +/** + * \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 + * \tparam T The type used to store the gain values when loaded from tuning file + * \tparam U The fixedpoint format used to convert gain values generated by + * polynomial expansion to the register format + * + * Defines the interface for the lsc algorithm implementation. Currently + * implemented by LscGrid and LscPolynomial. + */ + +/** + * \fn LscImplementation::~LscImplementation + * \brief Virtual class destructor + */ + +/** + * \fn LscImplementation::parseLscData + * \brief Parse \a tuningData using \a descriptor + * \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 + */ + +/** + * \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 + * \param[in] yPos List of vertical positions of the lsc grid nodes + * + * Lsc tables are expressed in two formats: + * - A list of gain values (LscGrid) + * - A radial polynomial (LscPolynomial) + * + * Grid-based lsc data 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. + * + * Polynomial are more flexible and can be re-sampled for a given resolution + * using a list of horizontal and vertical nodes that define the lsc grid. + * Polynomial lsc tables have to be re-sampled every time a new configuration is + * applied, as each streaming session might use a different sensor crop + * rectangle. + * + * \a cropRectangle represents the size of the frame on which the polynomial Lsc + * has to be re-sampled on. + * + * \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 + * 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..aecae305aa9c --- /dev/null +++ b/src/ipa/libipa/lsc_base.h @@ -0,0 +1,59 @@ +/* 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 "libcamera/internal/value_node.h" + +namespace libcamera { + +namespace ipa { + +namespace lsc { + +template +class Components : public std::map> +{ +}; + +template +class ComponentsMap : public std::map> +{ +}; + +} /* namespace lsc */ + +struct LscDescriptor { + std::vector keys; + unsigned int numHCells; + unsigned int numVCells; + Size sensorSize; +}; + +template +class LscImplementation +{ +public: + virtual ~LscImplementation() = default; + + virtual int parseLscData(const ValueNode &tuningData, + const LscDescriptor &descriptor) = 0; + + virtual lsc::ComponentsMap resampleLscData(const Rectangle &cropRectangle, + const std::vector &xPos, + const std::vector &yPos) = 0; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_grid.cpp b/src/ipa/libipa/lsc_grid.cpp new file mode 100644 index 000000000000..878994acc8c9 --- /dev/null +++ b/src/ipa/libipa/lsc_grid.cpp @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Grid based lens shading correction + */ + +#include "lsc_grid.h" + +/** + * \file lsc_grid.h + * \brief LscGrid class + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(LscGrid) + +namespace ipa { + +/** + * \class LscGrid + * \brief Grid based lsc algorithm implementation + * + * Grid based lsc algorithm implementation. The LscGrid class implements lsc + * support using tabular lsc data. + * + * \sa LscImplementation + * \sa LscAlgorithm + */ + +} /* namespace ipa */ +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_grid.h b/src/ipa/libipa/lsc_grid.h new file mode 100644 index 000000000000..e92cde95d18d --- /dev/null +++ b/src/ipa/libipa/lsc_grid.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Grid based lens shading correction + */ + +#pragma once + +#include + +#include + +#include "lsc_base.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(LscGrid) + +namespace ipa { + +template +class LscGrid : public LscImplementation +{ +public: + ~LscGrid() {} + + int parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor) override + { + const auto &sets = yamlSets.asList(); + + for (const auto &yamlSet : sets) { + uint32_t ct = yamlSet["ct"].get(0); + + int ret = parseLscComponent(yamlSet, ct, descriptor); + if (ret) + return ret; + } + + if (lscData_.empty()) { + LOG(LscGrid, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; + } + + lsc::ComponentsMap resampleLscData([[maybe_unused]] const Rectangle &cropRectangle, + [[maybe_unused]] const std::vector &xPos, + [[maybe_unused]] const std::vector &yPos) override + { + /* No resampling for grid-based LSC algorithm. */ + 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(LscGrid, Error) + << "Set " << k << " for color temperature " + << ct << " is missing"; + return -EINVAL; + } + } + + auto [it, inserted] = lscData_.emplace(ct, component); + if (!inserted) { + LOG(LscGrid, 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(LscGrid, Error) + << "Invalid '" << prop << "' values: expected " + << kLscNumSamples + << " elements, got " << table.size(); + return {}; + } + + return table; + } + + lsc::ComponentsMap lscData_; +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp index f607d86c54c3..8d3f1883d6a5 100644 --- a/src/ipa/libipa/lsc_polynomial.cpp +++ b/src/ipa/libipa/lsc_polynomial.cpp @@ -1,12 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) 2024, Ideas On Board + * Copyright (C) 2026, Ideas On Board * - * Polynomial class to represent lens shading correction + * Polynomial based lens shading correction */ #include "lsc_polynomial.h" +#include + #include /** @@ -21,7 +23,7 @@ LOG_DEFINE_CATEGORY(LscPolynomial) namespace ipa { /** - * \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,13 @@ namespace ipa { */ /** - * \fn LscPolynomial::sampleAtNormalizedPixelPos(double x, double y) + * \fn Polynomial::Polynomial(const Polynomial &other) + * \brief Construct a Polynomial by copy + * \param[in] other The Polynomial to copy construct from + */ + +/** + * \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 @@ -55,9 +63,21 @@ namespace ipa { * \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 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 @@ -66,9 +86,18 @@ namespace ipa { * * \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 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 @@ -76,6 +105,92 @@ namespace ipa { * * \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; +} + +/** + * \class LscPolynomialBase + * \brief Base class for LscPolynomial + * + * Base class for LscPolynomial for non-templated functions. + */ + +/** + * \brief Parse polynomial lsc data + * \param[in] yamlSets The tuning file content + * \param[in] descriptor The lsc engine descriptor + * + * Parse the lsc data in polyomial form from the \a yamlSet tuning data. + */ +int LscPolynomialBase::parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor) +{ + const auto &sets = yamlSets.asList(); + for (const auto &yamlSet : sets) { + uint32_t ct = yamlSet["ct"].get(0); + + Components components; + for (auto &k : descriptor.keys) { + auto polynomial = yamlSet[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); + } + + auto [it, inserted] = lscData_.emplace(ct, components); + if (!inserted) { + LOG(LscPolynomial, Error) + << "Multiple sets found for " + << "color temperature " << ct; + return -EINVAL; + } + } + + if (lscData_.empty()) { + LOG(LscPolynomial, Error) << "Failed to load any sets"; + return -EINVAL; + } + + return 0; +} + +/** + * \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 + * + * Polynomial-based lsc algorithm implementation. The LscPolynomial class + * implements lsc support using Polynomial to represent the shading artifacts + * map. + * + * \sa LscImplementation + * \sa LscAlgorithm + * + */ -} // namespace ipa -} // namespace libcamera +} /* namespace ipa */ +} /* namespace libcamera */ diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h index d7d9ae42e360..72cca591f260 100644 --- a/src/ipa/libipa/lsc_polynomial.h +++ b/src/ipa/libipa/lsc_polynomial.h @@ -1,15 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* - * Copyright (C) 2024, Ideas On Board + * Copyright (C) 2026, 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 @@ -18,64 +20,117 @@ #include "libcamera/internal/value_node.h" +#include "lsc_base.h" + namespace libcamera { LOG_DECLARE_CATEGORY(LscPolynomial) namespace ipa { -class LscPolynomial +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 }) { } - double sampleAtNormalizedPixelPos(double x, double y) const + Polynomial(const Polynomial &other) = default; + + double sampleAtNormalizedPixelPos(double x, double y) const; + double getM() const; + void setReferenceImageSize(const Size &size); + +private: + double cx_; + double cy_; + double cnx_; + double cny_; + std::array coefficients_; + Size imageSize_; +}; + +class LscPolynomialBase +{ +private: + using Components = std::map; + using ComponentsMap = std::map; + +public: + int parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor); + +protected: + ComponentsMap lscData_; +}; + +template +class LscPolynomial : public LscPolynomialBase, public LscImplementation +{ +public: + ~LscPolynomial() {} + + int parseLscData(const ValueNode &yamlSets, + const LscDescriptor &descriptor) override { - 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; + return LscPolynomialBase::parseLscData(yamlSets, descriptor); } - double getM() const + lsc::ComponentsMap resampleLscData(const Rectangle &cropRectangle, + const std::vector &xPos, + const std::vector &yPos) override { - 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)); + lsc::ComponentsMap components; - return sqrt(mx * mx + my * my); - } + for (auto &[t, c] : lscData_) { + lsc::Components comp; - void setReferenceImageSize(const Size &size) - { - assert(!size.isNull()); - imageSize_ = size; + for (auto &[k, p] : c) { + comp.emplace(std::piecewise_construct, + std::forward_as_tuple(k), + std::forward_as_tuple(samplePolynomial(p, xPos, yPos, + cropRectangle))); + } - /* Calculate normalized centers */ - double m = getM(); - cnx_ = (size.width * cx_) / m; - cny_ = (size.height * cy_) / m; + components[t] = comp; + } + + return components; } private: - double cx_; - double cy_; - double cnx_; - double cny_; - std::array coefficients_; + std::vector samplePolynomial(const Polynomial &poly, + Span xPositions, + Span yPositions, + const Rectangle &cropRectangle) - Size imageSize_; + { + 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 */ @@ -83,8 +138,8 @@ private: #ifndef __DOXYGEN__ template<> -struct ValueNode::Accessor { - std::optional get(const ValueNode &obj) const +struct ValueNode::Accessor { + std::optional get(const ValueNode &obj) const { std::optional cx = obj["cx"].get(); std::optional cy = obj["cy"].get(); @@ -98,7 +153,7 @@ struct ValueNode::Accessor { LOG(LscPolynomial, Error) << "Polynomial is missing a parameter"; - return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); + return ipa::Polynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4); } }; diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index edf8eabd8b78..da6ea0c5e130 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -14,6 +14,9 @@ libipa_headers = files([ 'fixedpoint.h', 'histogram.h', 'interpolator.h', + 'lsc.h', + 'lsc_base.h', + 'lsc_grid.h', 'lsc_polynomial.h', 'lux.h', 'module.h', @@ -36,6 +39,8 @@ libipa_sources = files([ 'fixedpoint.cpp', 'histogram.cpp', 'interpolator.cpp', + 'lsc.cpp', + 'lsc_grid.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 25f7c512b35c..51076737ffa5 100644 --- a/src/ipa/rkisp1/algorithms/lsc.cpp +++ b/src/ipa/rkisp1/algorithms/lsc.cpp @@ -2,7 +2,7 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * RkISP1 Lens Shading Correction control + * RkISP1 Lens Shading Correction algorithm */ #include "lsc.h" @@ -14,56 +14,17 @@ #include #include -#include "libcamera/internal/value_node.h" - -#include "libipa/lsc_polynomial.h" -#include "linux/rkisp1-config.h" - /** * \file lsc.h */ 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 rkisp1::algorithms::LensShadingCorrection::Components &a, - const rkisp1::algorithms::LensShadingCorrection::Components &b, - rkisp1::algorithms::LensShadingCorrection::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 { /** * \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 YAML tuning file. */ LOG_DEFINE_CATEGORY(RkISP1Lsc) @@ -72,265 +33,20 @@ namespace { constexpr int kColourTemperatureQuantization = 10; -class LscPolynomialShadingDescriptor : public LensShadingCorrection::ShadingDescriptor -{ -public: - LscPolynomialShadingDescriptor(const LscPolynomial &pr, const LscPolynomial &pgr, - const LscPolynomial &pgb, const LscPolynomial &pb) - : pr_(pr), pgr_(pgr), pgb_(pgb), pb_(pb) - { - } - - LensShadingCorrection::Components sampleForCrop(const Rectangle &cropRectangle, - Span xSizes, - Span ySizes) override; - -private: - std::vector samplePolynomial(const LscPolynomial &poly, - Span xPositions, - Span yPositions, - const Rectangle &cropRectangle); - - std::vector sizesListToPositions(Span sizes); - - LscPolynomial pr_; - LscPolynomial pgr_; - LscPolynomial pgb_; - LscPolynomial pb_; -}; - -LensShadingCorrection::Components -LscPolynomialShadingDescriptor::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) - }; -} - -std::vector -LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &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 -LscPolynomialShadingDescriptor::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 LscPolynomialLoader -{ -public: - LscPolynomialLoader(const Size &sensorSize) - : sensorSize_(sensorSize) - { - } - - int parseLscData(const ValueNode &yamlSets, - LensShadingCorrection::ShadingDescriptorMap &lscData); - -private: - Size sensorSize_; -}; - -int LscPolynomialLoader::parseLscData(const ValueNode &yamlSets, - LensShadingCorrection::ShadingDescriptorMap &lscData) +unsigned int quantize(unsigned int value, unsigned int step) { - const auto &sets = yamlSets.asList(); - for (const auto &yamlSet : sets) { - std::optional pr, pgr, pgb, pb; - uint32_t ct = yamlSet["ct"].get(0); - - if (lscData.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for " - << "color temperature " << ct; - return -EINVAL; - } - - pr = yamlSet["r"].get(); - pgr = yamlSet["gr"].get(); - pgb = yamlSet["gb"].get(); - pb = yamlSet["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; + return std::lround(value / static_cast(step)) * step; } -class LscTableShadingDescriptor : public LensShadingCorrection::ShadingDescriptor -{ -public: - LscTableShadingDescriptor(LensShadingCorrection::Components components) - : lscData_(std::move(components)) - { - } - - LensShadingCorrection::Components - 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: - LensShadingCorrection::Components lscData_; -}; - -class LscTableLoader -{ -public: - int parseLscData(const ValueNode &yamlSets, - LensShadingCorrection::ShadingDescriptorMap &lscData); - -private: - std::vector parseTable(const ValueNode &tuningData, - const char *prop); -}; - -int LscTableLoader::parseLscData(const ValueNode &yamlSets, - LensShadingCorrection::ShadingDescriptorMap &lscData) -{ - const auto &sets = yamlSets.asList(); - - for (const auto &yamlSet : sets) { - uint32_t ct = yamlSet["ct"].get(0); - - if (lscData.count(ct)) { - LOG(RkISP1Lsc, Error) - << "Multiple sets found for color temperature " - << ct; - return -EINVAL; - } - - LensShadingCorrection::Components set; - set.r = parseTable(yamlSet, "r"); - set.gr = parseTable(yamlSet, "gr"); - set.gb = parseTable(yamlSet, "gb"); - set.b = parseTable(yamlSet, "b"); - - if (set.r.empty() || set.gr.empty() || - set.gb.empty() || set.b.empty()) { - LOG(RkISP1Lsc, Error) - << "Set for color temperature " << ct - << " is missing tables"; - return -EINVAL; - } - - lscData.emplace( - ct, std::make_unique(std::move(set))); - } - - if (lscData.empty()) { - LOG(RkISP1Lsc, Error) << "Failed to load any sets"; - return -EINVAL; - } - - return 0; -} +} /* namespace */ -std::vector LscTableLoader::parseTable(const ValueNode &tuningData, - const char *prop) +LensShadingCorrection::LensShadingCorrection() + : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) { - 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) +std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningData, + const char *prop) { std::vector sizes = tuningData[prop].get>().value_or(utils::defopt); @@ -359,22 +75,36 @@ std::vector parseSizes(const ValueNode &tuningData, return sizes; } -unsigned int quantize(unsigned int value, unsigned int step) +/* + * 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::sizesToPositions(Span sizes) { - return std::lround(value / static_cast(step)) * step; -} + const int half = sizes.size(); + std::vector positions(half * 2 + 1); + double x = 0.0; -} /* namespace */ + 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; + } -LensShadingCorrection::LensShadingCorrection() - : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) -{ + return positions; } /** * \copydoc libcamera::ipa::Algorithm::init */ -int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, +int LensShadingCorrection::init(IPAContext &context, const ValueNode &tuningData) { xSize_ = parseSizes(tuningData, "x-size"); @@ -383,56 +113,27 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, if (xSize_.empty() || ySize_.empty()) return -EINVAL; - /* Get all defined sets to apply. */ - const ValueNode &yamlSets = tuningData["sets"]; - if (!yamlSets.isList()) { - LOG(RkISP1Lsc, Error) - << "'sets' parameter not found in tuning file"; - return -EINVAL; - } - - ShadingDescriptorMap lscData; - int ret = 0; - - std::string type = tuningData["type"].get("table"); - if (type == "table") { - LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data."; - auto loader = LscTableLoader(); - ret = loader.parseLscData(yamlSets, lscData); - } 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(yamlSets, lscData); - } else { - LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '" - << type << "'"; - ret = -EINVAL; - } - - if (ret) - return ret; + xPos_ = sizesToPositions(xSize_); + yPos_ = sizesToPositions(ySize_); - context.ctrlMap[&controls::LensShadingCorrectionEnable] = - ControlInfo(false, true, true); - - shadingDescriptors_ = std::move(lscData); - - return 0; + 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 + }); } /** * \copydoc libcamera::ipa::Algorithm::configure */ int LensShadingCorrection::configure(IPAContext &context, - [[maybe_unused]] const IPACameraSensorInfo &configInfo) + const IPACameraSensorInfo &configInfo) { const Size &size = context.configuration.sensor.size; Size totalSize{}; + /* Calculate gradients. */ for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE; ++i) { xSizes_[i] = xSize_[i] * size.width; ySizes_[i] = ySize_[i] * size.height; @@ -455,16 +156,8 @@ int LensShadingCorrection::configure(IPAContext &context, yGrad_[i] = std::round(32768 / ySizes_[i]); } - 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_); - - 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) @@ -476,12 +169,16 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config) } void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config, - const Components &set) + const ipa::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]); } /** @@ -492,19 +189,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); } /** @@ -542,7 +228,7 @@ void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, setParameters(*config); - const Components &set = sets_.getInterpolated(quantizedCt); + const auto &set = lscAlgo_.interpolateComponents(quantizedCt); copyTable(*config, set); lastAppliedCt_ = ct; @@ -562,7 +248,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 0a256e225327..7c656d95acfb 100644 --- a/src/ipa/rkisp1/algorithms/lsc.h +++ b/src/ipa/rkisp1/algorithms/lsc.h @@ -2,17 +2,23 @@ /* * Copyright (C) 2021-2022, Ideas On Board * - * RkISP1 Lens Shading Correction control + * RkISP1 Lens Shading Correction algorithm */ #pragma once -#include -#include +#include -#include "libipa/interpolator.h" +#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 { @@ -37,39 +43,28 @@ public: 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: + std::vector parseSizes(const ValueNode &tuningData, + const char *prop); + std::vector sizesToPositions(Span sizes); + 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 ipa::lsc::Components &set0); - ShadingDescriptorMap shadingDescriptors_; - ipa::Interpolator sets_; std::vector xSize_; std::vector ySize_; 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]; uint16_t ySizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE]; + std::vector xPos_; + std::vector yPos_; unsigned int lastAppliedCt_; unsigned int lastAppliedQuantizedCt_; + + LscAlgorithm> lscAlgo_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index cd97e10bcf2b..005f4102b4f6 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 { @@ -138,9 +139,7 @@ struct IPAActiveState { double strength; } wdr; - struct { - bool enabled; - } lsc; + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -213,10 +212,7 @@ struct IPAFrameContext : public FrameContext { double gain; } wdr; - struct { - bool enabled; - bool update; - } lsc; + ipa::lsc::FrameContext lsc; }; struct IPAContext { From patchwork Mon Jun 15 14:05: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: 26888 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 E590FC32D4 for ; Mon, 15 Jun 2026 14:06:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 088A46259F; Mon, 15 Jun 2026 16:06:05 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AQZqy2QQ"; 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 A8BF3623DF for ; Mon, 15 Jun 2026 16:05:49 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DB2381E1B; Mon, 15 Jun 2026 16:05:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532317; bh=iRwJ9DOf8LDIu4m01y5qwYX2y5eZOKtzRgCGcBCoY7U=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=AQZqy2QQBUeSQ6nbnNSmavmnxsKf2obVZenZYT15z2e/GCn9/3lRy2dDpxsAaeD1j mBvNoQU+Y8p6aUcBJCPjQuVW6vInOSYzi0F7gEwpgFCkRT1lEGIqw2te/G2PBV5fs0 CnyPsLDjVN6rnsB6KHT/3/9XFf+htNBZ8WWGosCM= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:35 +0200 Subject: [PATCH 10/11] ipa: mali-c55: Add sensorInfo to IPAContext MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-10-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1478; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=iRwJ9DOf8LDIu4m01y5qwYX2y5eZOKtzRgCGcBCoY7U=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa6W3VvpdsTUdY5JXnDvPzSIouEBmKroo7Nj 56Cn7rQFJaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGugAKCRByNAaPFqFW PDbfD/9y7cFe+oMbl2yxNf2PYoKAaoUCB9A5CO+FTHKeirgOpSKT0RssxbOdYgK26Hasv/4RW+g Q0c3Xy5AEAoXW6QJ25dn91kfAJ1lau0gyqs6hmIoNXuGVyeNr4ipiI5zuJhS0wJs2mU27PhAMpL MiCmSSSbbarpVfeDFNBK/2FSSBzEMP+QQ/6YPpEs0NJp/wWyA3uQMzZMigyMFJRA0Js289cVPv0 2awG9w0xrCev8VR5Virt5G7rB8j8Xt6A4Hv8wwi8v3BAPFSb3gzFka2uphQhQ2ikcnI+qcxcItQ YFq5U7dEVHydQM+YlCE6LJbE5vB0Y3U1n4OnGNQlJOIK+AWv9NeRANnQqitIwFDFXIkED49Xk78 nF6bHcL7JQw+wUf6KDNETfkyzWt0NLRx1wJY3X3wKn5zq4whr9Xd9Rta7CscpOX8A/iP1u0ybKo ZlqoY8dv80AQU58AZomjB8ZEirsd3/yjA/9aWhsGXditr00tk4nOxdt1DtqtU2DTrFzhP5Z6rhS e960Zzqp/L55sZAbkf3t8X6SQ/AJ3Pjf6BIObu2YCX3y2C0S10gL7RXNHI7jlAKBKRKyiVt/3K0 uk5j/aGEGzJxSEIfsBCRuSNX13PsPKDxMZOX62tV6xDvgTR/E9mHOGDdPKlD24IAWCzUEpyCqJ8 9ze+rdILk9sT0Bg== 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 --- 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 2d3e91d56baa..2f2092677fbf 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 @@ -82,6 +84,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 Jun 15 14:05: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: 26887 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 271FAC324C for ; Mon, 15 Jun 2026 14:06:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1548D623ED; Mon, 15 Jun 2026 16:06:04 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="P4Zx/bEx"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EA520623E3 for ; Mon, 15 Jun 2026 16:05:49 +0200 (CEST) Received: from [192.168.1.104] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2DCC51E2B; Mon, 15 Jun 2026 16:05:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781532317; bh=zg4cmPp5HRWt/gyBfY1mXsplO4CkJuGZjtP200olsWE=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=P4Zx/bExvvXFPnfYemcS8CglYiQ6QsGB+jAwGIxA06mUTKuYxREXg3KTHqN7/42a+ sZ+rM9jXWUF270GLxq42QGTUiwG+J6sVkDn22zYpHfc90rXKO0JvvpUwomWg7simK3 5uukTYaOiBtOm13ZQVPJmodeG27fEZPfDqIHgOo0= From: Jacopo Mondi Date: Mon, 15 Jun 2026 16:05:36 +0200 Subject: [PATCH 11/11] ipa: mali-c55: Port to use LscAlgorithm MIME-Version: 1.0 Message-Id: <20260615-libipa-algorithms-v1-11-e949c937422e@ideasonboard.com> References: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> In-Reply-To: <20260615-libipa-algorithms-v1-0-e949c937422e@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=11475; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=zg4cmPp5HRWt/gyBfY1mXsplO4CkJuGZjtP200olsWE=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqMAa6MHKpTNv5+E66H6HJ8AwDf0WMxTRLo6ao9 +YrPmsU+16JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCajAGugAKCRByNAaPFqFW PAxxD/4onSo6uXwWiIyubkpsLolF1UhGfqMnTSyrTRsoRz+gJLnfp72vE1j4Hzx8MDvQCuJYnUa 3gtoJBmcJ76pHkWXQHKxwVHKPQ9pj4NRf8Xbr9DASDMD0kcaxyg5ga48ZGEZlvcWhm2fYKeMUBK KSvob7dTKzTCmBaIJVRlXWb9zcPmIZdudO/RFeVF6DcNLM4EVhUkuuoknjfUfdyusb0H6DjwHdj nVZkzkvm0DvYmgN2sqe3EGbx1PhHXgIvxJ3rZD8+cKpAbbBAUwrs1HiuD+1GEgMG2sMHZlMVyjQ mThh5H871++5PKqYAOzChwP0AXv0/ibI9ed0LB0CyO6+LBFa067Dp0qu5FV56mI17VvRDo3XigB tnM5nx45Se5vWnjIb+j4fyEDcEN2go9bPEQzlrm7Iy5NQDkkvxAbAFi71/+lXd0VTcmWDgQczEo QA8kgmOv3oZLGJIc9B6USAtDljerR1eE0D+VOMn9mI//NmikBwQermTCAd7TCJohYbFhgd/us/3 zTrgwBWXFEFG7mCUYL+qSgQM6OKaT3h1NkGy/3hmMJWsdVr/nTTgbx5KdBzcL4NVurYsKEg31KI 7598dkdyhDvrlo6hPXhB8BTo5nBTAT5i1ZLKYp1frq7RaGV1wPwlJsINt5B43zhBvz3HYVPJqEf ZrxpIsF879P/wFA== 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 --- src/ipa/mali-c55/algorithms/lsc.cpp | 163 ++++++++++++++++++++++-------------- src/ipa/mali-c55/algorithms/lsc.h | 38 ++++++--- src/ipa/mali-c55/ipa_context.h | 5 ++ 3 files changed, 130 insertions(+), 76 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp index 36e50163aecc..624b393b80e5 100644 --- a/src/ipa/mali-c55/algorithms/lsc.cpp +++ b/src/ipa/mali-c55/algorithms/lsc.cpp @@ -9,39 +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 Mali-C55 supports up to 4 colour temperatures. + * + * Unfortunately 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 kMaxColourTemperature = 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 &yamlSets = tuningData["sets"]; - if (!yamlSets.isList()) { - LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid"; - return -EINVAL; - } +int Lsc::init([[maybe_unused]] 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; - const auto &sets = yamlSets.asList(); - for (const auto &yamlSet : sets) { - uint32_t ct = yamlSet["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 * kMaxColourTemperature); + 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) @@ -49,44 +84,29 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData) return -EINVAL; } - std::vector rTable = - yamlSet["r"].get>().value_or(utils::defopt); - std::vector gTable = - yamlSet["g"].get>().value_or(utils::defopt); - std::vector bTable = - yamlSet["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() >= kMaxColourTemperature) { 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] |= @@ -98,34 +118,35 @@ 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; } -void Lsc::fillConfigParamsBlock(MaliC55Params *params) const +/** + * \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::programMeshTables(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, +void Lsc::configureMeshBlending(MaliC55Params *params, uint8_t bank, uint8_t alpha) const { auto block = params->block(); @@ -187,7 +208,7 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, std::tie(bank, alpha) = findBankAndAlpha(temperatureK); } - fillSelectionParamsBlock(params, bank, alpha); + configureMeshBlending(params, bank, alpha); if (frame > 0) return; @@ -196,7 +217,19 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, * If this is the first frame, we need to load the parsed coefficient * tables from tuning data to the ISP. */ - fillConfigParamsBlock(params); + programMeshTables(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") diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h index 3d35fd72bfa8..79ad42094c42 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; - - void fillConfigParamsBlock(MaliC55Params *params) const; - void fillSelectionParamsBlock(MaliC55Params *params, - uint8_t bank, uint8_t alpha) const; + std::vector segmentsToPosition() const; + void programMeshTables(MaliC55Params *params) const; + void configureMeshBlending(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 2f2092677fbf..749309115d84 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 { @@ -64,6 +65,8 @@ struct IPAActiveState { ipa::awb::ActiveState awb; ipa::ccm::ActiveState ccm; + + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -76,6 +79,8 @@ struct IPAFrameContext : public FrameContext { ipa::awb::FrameContext awb; ipa::ccm::FrameContext ccm; + + ipa::lsc::FrameContext lsc; }; struct IPAContext {