From patchwork Mon Sep 14 14:53:46 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dan Scally X-Patchwork-Id: 28293 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 E6554C328D for ; Mon, 14 Sep 2026 14:54:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 18D45686C4; Mon, 14 Sep 2026 16:54:10 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ByJP8y5+"; 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 5470D686BE for ; Mon, 14 Sep 2026 16:53:57 +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 3E8EC1CE6; Mon, 14 Sep 2026 16:52:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1789397537; bh=mj40lWOHTOCPjMOUF3ooS/cgK88lmUY5eLgUx7GRUQM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=ByJP8y5+7qMcxlQfkTWm1nAZVK2SQaCfnynPT85ID7xGQiAUyMA9itEUOu5P7v/Z0 RvIJ3F1NcORtqfrftx+rj+ZNvXDACgA8wGVQjjK+oV8L6agEjsIk7jLIBy+bSRK6G/ zgT/h5hKoCtyPhEaSTgAowAM5I+GG+GsEAhxZLbQ= From: Daniel Scally Date: Mon, 14 Sep 2026 15:53:46 +0100 Subject: [PATCH v4 06/10] ipa: libipa: Add GammaAlgorithm class MIME-Version: 1.0 Message-Id: <20260914-ipu3-libipa-rework-v4-6-ff8ee2d3dd24@ideasonboard.com> References: <20260914-ipu3-libipa-rework-v4-0-ff8ee2d3dd24@ideasonboard.com> In-Reply-To: <20260914-ipu3-libipa-rework-v4-0-ff8ee2d3dd24@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Daniel Scally , Kieran Bingham , Jacopo Mondi X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=12448; i=dan.scally@ideasonboard.com; h=from:subject:message-id; bh=mj40lWOHTOCPjMOUF3ooS/cgK88lmUY5eLgUx7GRUQM=; b=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqqAqBRTEIlhtenRYrJy7AhUNhDEIigQ+ut86uK 13akG0IiTCJAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCaqgKgQAKCRDISVd6bEV1 Ms/oD/0aqA6RZyFatPOdUnOjx8vrRKbjQmzehqPGKZ1DpbRqoK8k6uufgPeqcYr8rU0m8QNLMnB qcq6g2b5WANiROgjdBJHtTuKUj/vzP92psURh32MYpzkyhXL2w5t4BnLuKS8lcrUCl2BynHXpI6 jfUogp2M2FeB3e/tjyZaQg9qmPa9Rz+ihRUB7X5GSR2PyO8n4RcYyjd1jhYTy1oeC8kAYGC/V+B wLB0O4ptoOP3iVsi53J5fCnEp8/2DkdUwdsAXJ4KMXOUQfDcB+DhViycitELWtbe6p9JzQN3L+a m2QWHND4nKBFCI9sx66RnIGvNYGuYPaLgauYPQKmP78HxzmSfyIXivEp+pQc9B7NgrQDdGI1SzE y55GKKcoRDtc+czCT18NgkwLL1fdkzkx7YScCE54OxxEgcZV6A6E1TXQZQWXQTPVOugp/CMqv5f KOYz4qLv55pezwJJ1FAG+/zoPY3RzU48wCFRTLr/zZf7SmuqWD206AMJKg1wKXsvkX71illcF5A xlk+mFjEZE0p7wQZZfEEcJ2G/vnZK5vnvPybL5fa5Pa8QouuIJU/O1OOHmSNQoLohKWWXLDzQWI Fl3bee8v1dHCIjnXX39hJxcqOaJ8t/Gh006cFOzX8zpdDRFO1dAhEtRhR4/0zvZa4fhRH4RczdN RAs61teJPzUukwQ== 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. Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi Signed-off-by: Daniel Scally --- Changes in v4: - Use std::span in place of libcamera's Span class - Switch to float in place of double - Re-work things so that the ::init() function stores the x-coordinate of the knee-points instead of the segment spacings, which avoids some calculation in ::prepare() Changes in v3: - Require a size template parameter for the Span passed to prepare() Changes in v2: - Updated documentation comments, and minor styling changes --- src/ipa/libipa/gamma.cpp | 259 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/gamma.h | 87 +++++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 348 insertions(+) diff --git a/src/ipa/libipa/gamma.cpp b/src/ipa/libipa/gamma.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ce66f15d6a1d3017cc070ef77db494c359441cac --- /dev/null +++ b/src/ipa/libipa/gamma.cpp @@ -0,0 +1,259 @@ +/* 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 + */ + +/** + * \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. The piecewise linear + * function will be applied on a number of knots whose position is described by + * the optional \a segments argument, which describes each segment's relative + * length. + * + * For example, if the gamma correction has to be applied on 16 equally spaced + * sampling points, a \a segments array like: + * + * [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. + *; + * If an IPA implementation doesnt't provide \a segments, the GammaAlgorithm + * class consturcts an evenly-spaced default. + * + * 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, + std::span segments) +{ + /* + * If the caller doesn't pass in a segment list we simply construct the + * position of the knee-points assuming equally spaced segments. + */ + if (segments.empty()) { + for (unsigned int i = 0; i < nLutNodes_; i++) + kneePoints_[i] = static_cast(i) / (nLutNodes_ - 1); + } else { + /* + * As segments holds the distance between the knee-points, we + * expect one fewer segment entries than we have LUT nodes. + */ + if (segments.size() != nLutNodes_ - 1) + return -EINVAL; + + float total = std::accumulate(segments.begin(), segments.end(), 0.0f); + float x = 0.0f; + + for (unsigned int i = 0; i < nLutNodes_; i++) { + kneePoints_[i] = x / total; + + if (i < segments.size()) + x += segments[i]; + } + } + + 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 gamma LUT sampling points + */ + +/** + * \var GammaAlgorithmBase::defaultGamma_ + * \brief The default gamma parameter + */ + +/** + * \var GammaAlgorithmBase::kneePoints_ + * \brief a vector holding the X-position of the knee points of the curve + */ + +/** + * \class GammaAlgorithm + * \brief The libipa gamma correction algorithm + * \tparam NLutNodes The number of gamma LUT sampling points + * \tparam UQ The fixedpoint representation of the gamma correction 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. + * + * Useful links: + * - https://www.cambridgeincolour.com/tutorials/gamma-correction.htm + * - https://en.wikipedia.org/wiki/SRGB + */ + +/** + * \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 std::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..9954f94ba244841f1253bc07ab8a44e0180d378a --- /dev/null +++ b/src/ipa/libipa/gamma.h @@ -0,0 +1,87 @@ +/* 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 { + float gamma; +}; + +struct FrameContext { + float gamma; + bool update; +}; + +} /* namespace gamma */ + +class GammaAlgorithmBase +{ +public: + GammaAlgorithmBase(unsigned int nLutNodes) + : nLutNodes_(nLutNodes), kneePoints_(nLutNodes) + { + } + + int init(ControlInfoMap::Map &controls, const ValueNode &tuningData, + std::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 kneePoints_; +}; + +template +class GammaAlgorithm : public GammaAlgorithmBase +{ +public: + GammaAlgorithm() + : GammaAlgorithmBase(NLutNodes) + { + } + + template + void prepare(gamma::FrameContext &context, std::span lut) + { + for (unsigned int i = 0; i < nLutNodes_; i++) { + float gamma = std::pow(kneePoints_[i], 1.0f / context.gamma); + lut[i] = UQ(gamma).quantized(); + + LOG(Gamma, Debug) << "LUT[" << i << "]=" << gamma + << "(" << lut[i] << ")"; + } + } +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 0fb65a23931bdb3430f76843cd3a8d18fb5083d6..a38d5d528aac964d25c9ec2a06e10f6c4ed74494 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -14,6 +14,7 @@ libipa_headers = files([ 'exposure_mode_helper.h', 'fc_queue.h', 'fixedpoint.h', + 'gamma.h', 'histogram.h', 'interpolator.h', 'lsc.h', @@ -42,6 +43,7 @@ libipa_sources = files([ 'exposure_mode_helper.cpp', 'fc_queue.cpp', 'fixedpoint.cpp', + 'gamma.cpp', 'histogram.cpp', 'interpolator.cpp', 'lsc.cpp',