From patchwork Tue Jun 16 06:41:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dan Scally X-Patchwork-Id: 26896 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 0A84FC3301 for ; Tue, 16 Jun 2026 06:42:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2D731625A1; Tue, 16 Jun 2026 08:41:56 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BlM8gDMn"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D9A6C62401 for ; Tue, 16 Jun 2026 08:41:49 +0200 (CEST) Received: from [127.0.1.1] (chfd-03-b2-v4wan-176392-cust229.vm15.cable.virginm.net [82.19.20.230]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 977A4217B; Tue, 16 Jun 2026 08:41:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1781592076; bh=Y0ZLtyoOLcv4vmrV9hPoS/xsNd9dtxzJlb5kWBqmzBo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=BlM8gDMnjXThDiChj+dICzYm2GJCpFJYeVsKSN/6i4hGGmfOeaW9ahs9PBiGfgTan 6j+0U7fvNBkLDfkPJerwjdr0jVK38SKNTL1xLXle+voj8cN+AraeHPuR4ibypudFTx mbpOswPRutK/OqDArE0TCXT/FGXignkn7jXaP/EM= From: Daniel Scally Date: Tue, 16 Jun 2026 07:41:39 +0100 Subject: [PATCH 05/10] ipa: libipa: Add GammaAlgorithm class MIME-Version: 1.0 Message-Id: <20260616-ipu3-libipa-rework-v1-5-d4448b54f1d8@ideasonboard.com> References: <20260616-ipu3-libipa-rework-v1-0-d4448b54f1d8@ideasonboard.com> In-Reply-To: <20260616-ipu3-libipa-rework-v1-0-d4448b54f1d8@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Daniel Scally X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=11753; i=dan.scally@ideasonboard.com; h=from:subject:message-id; bh=Y0ZLtyoOLcv4vmrV9hPoS/xsNd9dtxzJlb5kWBqmzBo=; b=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqMPApaOrgoJUVlQLIGjxdg/LCTIODPR0RA9lad gfwS/rZxL6JAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCajDwKQAKCRDISVd6bEV1 MqiBD/0Su+//Jr0g2xbxxNUQCshEUQG/PK6953VhX7xg/E15uDgvc3RjEAOKWk8cyW4npzeq9up 6Qput34hu0ZWlGAU850r7CEXos/wr8aOiOr51KNXEdRyBsT+NcEQ/R/2koLrv5qS3Bp7B3MZBIz fgyRk2TG07VNlrAiZb10jQwUNkHxwLHLNy4QoUSky551mF7BV1AjMm9CaYnaFrlFakbGE0JfGDE NtaGN370sgfi/x8JaTPsPMeIX/o8MeQjXN7JKSLdyhpKDIIbu++OMjzdJEY2hxz0fBa9lbcP+Io ECVziX0OOOUDrmAdHxJD0rb9vd5sa0YNdT59BR0RqJG7d5gUdYI6mD2ez0fuLkHaMfVSr8c9Y6T n+IyTFaRpxl0762E92AwEr9kK65KIeoME5zuEkK1OJDBnwXaFaig7JkWcEwmREubdafwIcv3iB5 P8as6PUIrEf/nGQp3jKAHWOb3AkoNkaYu3sdC3XPpWNCB7cUF+3G1JGDWmMOIShRxPTsLpb8iFG EC/g0mhip6rFHmqQFGKAFcZo6xUGy5z10VyZSoRXRAdItKTtI46bZDFzFTsWGsAAHbckJElZEmO LWS7v8ec3PpeVTFcRxUde+LJyarWI+iBKLAb8EDe1uJa58oDrr04Ir3S+s/KCZoRpHq/7u7NnWO V+s2DaWRgnbnAdA== X-Developer-Key: i=dan.scally@ideasonboard.com; a=openpgp; fpr=EEC699ACA1B7CB5D31330C0BBD501C2A3546CCF6 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 base GammaAlgorithm class that can be used by IPA specific gamma algorithms to reduce the amount of work that they need to implement. Signed-off-by: Daniel Scally --- src/ipa/libipa/gamma.cpp | 248 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/gamma.h | 93 +++++++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 343 insertions(+) diff --git a/src/ipa/libipa/gamma.cpp b/src/ipa/libipa/gamma.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f84f4cf7049f695f6a4ea21e0bcf851aef0f2c70 --- /dev/null +++ b/src/ipa/libipa/gamma.cpp @@ -0,0 +1,248 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Gamma correction algorithm + */ + +#include "gamma.h" + +#include + +#include + +#include "libcamera/internal/value_node.h" + +/** + * \file gamma.h + * \brief libipa implementation of a gamma curve correction algorithm + */ + +namespace libcamera { + +namespace ipa { + +LOG_DEFINE_CATEGORY(Gamma) + +namespace gamma { + +/** + * \struct ActiveState + * \brief Active gamma correction algorithm state + * + * \var ActiveState::gamma + * \brief The gamma correction value applied as 1.0/gamma + */ + +/** + * \struct FrameContext + * \brief Per-frame gamma correction settings + * + * \var FrameContext::gamma + * \brief The gamma correction value applied for this frame + * + * \var FrameContext::update + * \brief A flag instructing the algorithm to push an update to the hardware + */ + +} /* namespace gamma */ + +/** + * \brief The default gamma correction value + */ +const float kDefaultGamma = 2.2f; + +/** + * \class GammaAlgorithmBase + * \brief Base class for GammaAlgorithm to implement non-templated functions + * + * This base class for GammeaAlgorithm allows us to implement non templated + * functions. IPA specific implementations shall derive from GammaAlgorithm and + * not this class. + */ + +/** + * \fn GammaAlgorithmBase::GammaAlgorithmBase + * \brief Construct an instance of the class + * \param[in] nLutNodes Set the number of function knee-points expected by the + * IPA algorithm + */ + +/** + * \brief Initialise the algorithm with the given tuning data + * \param[out] controls The ControlList into which this algorithm's supported + * controls will be emplaced. + * \param[in] tuningData The tuning data to use with the algorithm + * \param[in] segments A vector of segment spacings to define a custom + * X coordinate system for the curve + * + * Parse \a tuningData and \a segments to initialize the gamma correction curve. + * The tuning data may contain a default gamma value to use; otherwise the value + * of \a kDefaultGamma will be taken as the default. \a segments may provide a + * view into an array of segment spacings, which can be used to vary the X + * co-ordinate of the gamma correction curve. For example, if the piecewise + * linear function of the correction curve is expected to have 16 knee-points, a + * \a segments array like so: + * + * [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + * + * would result in evenly spaced knee-points along the X-axis. Hardware may + * expect the knee-points to be spaced more densely towards the start of the + * curve and more sparsely towards the end, in which case an alternative array + * might be: + * + * [1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 8, 8, 8, 8, 8] + * + * As the values in \a segments represent the distance between two knee-points + * relative to the total distance between the first and last point, the length + * of \a segments should be equal to the number of knee-points minus one. As a + * convenience, a hardware-specific algorithm deriving from this class may omit + * \a segments, in which case an evenly-spaced default will be constructed. + * + * 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 GammaAlgorithmBase::init(ControlInfoMap::Map &controls, const ValueNode &tuningData, + Span segments) +{ + /* + * If the caller doesn't pass in a segment list we simply construct one + * with equally spaced segments. We need one less segment than we have + * LUT nodes. + */ + unsigned int expectedNSegments = nLutNodes_ - 1; + + if (segments.empty()) + for (unsigned int i = 0; i < expectedNSegments; i++) + segments_.push_back(1); + else + segments_.assign(segments.begin(), segments.end()); + + if (segments_.size() != expectedNSegments) + return -EINVAL; + + segmentsSum_ = std::accumulate(segments_.begin(), segments_.end(), 0.0f); + + defaultGamma_ = tuningData["gamma"].get(kDefaultGamma); + controls[&controls::Gamma] = ControlInfo(0.1f, 10.0f, defaultGamma_); + + return 0; +} + +/** + * \brief Configure the gamma correction algorithm + * \param[out] state The gamma correction algorithm's active state + * + * Reset to the default gamma correction value. + * + * IPA modules are expected to call this function as part of their + * implementation of Algorithm::configure() + */ +void GammaAlgorithmBase::configure(gamma::ActiveState &state) +{ + state.gamma = defaultGamma_; +} + +/** + * \brief Queue a request to the gamma correction algorithm + * \param[in] state The algorithm's active state + * \param[in] frame The current frame number + * \param[in] context The algorithm's frame context + * \param[in] controls The ControlList that was queued with the request + * + * Queue a new request to the gamma correction algorithm and handle any relevant + * controls that were queued. The only control currently handled is: + * + * - controls::Gamma + * + * If a control with that ID is queued the value is stored in \a state and + * \a context. + * + * IPA modules are expected to call this function as part of their + * implementation of Algorithm::queueRequest() + */ +void GammaAlgorithmBase::queueRequest(gamma::ActiveState &state, + const uint32_t frame, + gamma::FrameContext &context, + const ControlList &controls) +{ + if (frame == 0) + context.update = true; + + const auto &gamma = controls.get(controls::Gamma); + if (gamma) { + state.gamma = *gamma; + context.update = true; + LOG(Gamma, Info) << "Set gamma to " << *gamma; + } + + context.gamma = state.gamma; +} + +/** + * \brief Populate metadata with the gamma correction values for a frame + * \param[in] context The frame context + * \param[out] metadata The ControlList of metadata for a frame + * + * Report the gamma value used to calculate the correction curve that was + * applied to a frame. + */ +void GammaAlgorithmBase::process(gamma::FrameContext &context, ControlList &metadata) +{ + metadata.set(controls::Gamma, context.gamma); +} + +/** + * \var GammaAlgorithmBase::nLutNodes_ + * \brief The number of knee-points in the gamma correction curve + */ + +/** + * \var GammaAlgorithmBase::defaultGamma_ + * \brief The default gamma parameter used at stream start + */ + +/** + * \var GammaAlgorithmBase::segments_ + * \brief The vector of segment sizes describing the space between knee-points + */ + +/** + * \var GammaAlgorithmBase::segmentsSum_ + * \brief The sum of \a GammaAlgorithmBase::segments_ + */ + +/** + * \class GammaAlgorithm + * \brief The libipa gamma correction algorithm + * \tparam nLutNodes The number of knee-points in the algorithm's function + * \tparam UQ The fixedpoint representation of the function's values + * + * Gamma correction adjusts for the differences in the way light is perceived + * by a camera and the human eye by applying a function to the input values. + * The GammaAlgorithm class facilitates this by building a piecewise linear + * function from a gamma parameter and supplying it in the hardware-specific + * formats defined by the IPA algorithms. + * + * IPA modules are expected to store an instance of GammaAlgorithm as a class + * member, templated with the format and number of knee-points in the PWL + * expected by their hardware and then call its functions in their overload of + * the Algorithm class's function. + * + * When an application queues a new value for the gamma parameter with a + * Request, the GammaAlgorithm will recalculate and populate the new LUT to be + * sent to the ISP. + */ + +/** + * \fn GammaAlgorithm::prepare() + * \tparam T The type of data expected by the hardware's look-up table + * \param[in] context The frame context + * \param[out] lut The Span into which to place the calculated look-up table + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/gamma.h b/src/ipa/libipa/gamma.h new file mode 100644 index 0000000000000000000000000000000000000000..2b449d9ec41bc96f576e664afca33acb8267a8e6 --- /dev/null +++ b/src/ipa/libipa/gamma.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA Gamma correction algorithm + */ + +#pragma once + +#include +#include + +#include +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "fixedpoint.h" + +namespace libcamera { + +namespace ipa { + +LOG_DECLARE_CATEGORY(Gamma) + +namespace gamma { + +struct ActiveState { + double gamma; +}; + +struct FrameContext { + double gamma; + bool update; +}; + +} /* namespace gamma */ + +class GammaAlgorithmBase +{ +public: + GammaAlgorithmBase(unsigned int nLutNodes) + : nLutNodes_(nLutNodes) + { + } + + int init(ControlInfoMap::Map &controls, const ValueNode &tuningData, + Span segments = {}); + + void configure(gamma::ActiveState &state); + void queueRequest(gamma::ActiveState &state, const uint32_t frame, + gamma::FrameContext &context, const ControlList &controls); + void process(gamma::FrameContext &context, ControlList &metadata); + +protected: + unsigned int nLutNodes_; + float defaultGamma_; + std::vector segments_; + unsigned int segmentsSum_; +}; + +template +class GammaAlgorithm : public GammaAlgorithmBase +{ +public: + GammaAlgorithm() + : GammaAlgorithmBase(nLutNodes) + { + } + + template + void prepare(gamma::FrameContext &context, Span lut) + { + float x = 0; + + for (unsigned int i = 0; i < nLutNodes_; i++) { + float gamma = std::pow(x / segmentsSum_, + 1.0 / context.gamma); + lut[i] = UQ(gamma).quantized(); + + LOG(Gamma, Debug) << "LUT[" << i << "]=" << gamma << "(" << lut[i] << ")"; + + if (i < segments_.size()) + x += segments_[i]; + } + } +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index da6ea0c5e13000f78b2196c7334610c350f1ad13..565da9be9059f2167d107061d643e58202e655ef 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -12,6 +12,7 @@ libipa_headers = files([ 'exposure_mode_helper.h', 'fc_queue.h', 'fixedpoint.h', + 'gamma.h', 'histogram.h', 'interpolator.h', 'lsc.h', @@ -37,6 +38,7 @@ libipa_sources = files([ 'exposure_mode_helper.cpp', 'fc_queue.cpp', 'fixedpoint.cpp', + 'gamma.cpp', 'histogram.cpp', 'interpolator.cpp', 'lsc.cpp',