new file mode 100644
@@ -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 <numeric>
+
+#include <libcamera/controls.h>
+
+#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<unsigned int> 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<double>(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 */
new file mode 100644
@@ -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 <cmath>
+#include <vector>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/span.h>
+
+#include <libcamera/control_ids.h>
+
+#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<unsigned int> 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<unsigned int> segments_;
+ unsigned int segmentsSum_;
+};
+
+template<unsigned int nLutNodes, typename UQ>
+class GammaAlgorithm : public GammaAlgorithmBase
+{
+public:
+ GammaAlgorithm()
+ : GammaAlgorithmBase(nLutNodes)
+ {
+ }
+
+ template<typename T>
+ void prepare(gamma::FrameContext &context, Span<T> 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 */
@@ -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',
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 <dan.scally@ideasonboard.com> --- src/ipa/libipa/gamma.cpp | 248 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/gamma.h | 93 +++++++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 343 insertions(+)