| Message ID | 20260626-ipu3-libipa-rework-v2-6-41546e23de3e@ideasonboard.com |
|---|---|
| State | New |
| Headers | show |
| Series |
|
| Related | show |
Quoting Daniel Scally (2026-06-26 14:05:53) > 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: Jacopo Mondi <jacopo.mondi@ideasonboard.com> I'm interested to see where this goes, but having a common block looks useful so: Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com> > --- > Changes in v2: > > - Updated documentation comments, and minor styling changes > --- > src/ipa/libipa/gamma.cpp | 257 +++++++++++++++++++++++++++++++++++++++++++++ > src/ipa/libipa/gamma.h | 94 +++++++++++++++++ > src/ipa/libipa/meson.build | 2 + > 3 files changed, 353 insertions(+) > > diff --git a/src/ipa/libipa/gamma.cpp b/src/ipa/libipa/gamma.cpp > new file mode 100644 > index 0000000000000000000000000000000000000000..5fec5329a50f0b3dd0e39504dfbb63484ca4fcd9 > --- /dev/null > +++ b/src/ipa/libipa/gamma.cpp > @@ -0,0 +1,257 @@ > +/* 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 > + */ > + > +/** > + * \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 \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, > + 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 gamma LUT sampling points > + */ > + > +/** > + * \var GammaAlgorithmBase::defaultGamma_ > + * \brief The default gamma parameter > + */ > + > +/** > + * \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 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 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..94be8c7a2328e7b5a3618b0d65989ee511cece09 > --- /dev/null > +++ b/src/ipa/libipa/gamma.h > @@ -0,0 +1,94 @@ > +/* 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 */ > 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', > > -- > 2.43.0 >
2026. 06. 26. 15:05 keltezéssel, Daniel Scally írta: > 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: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com> > --- > Changes in v2: > > - Updated documentation comments, and minor styling changes > --- > src/ipa/libipa/gamma.cpp | 257 +++++++++++++++++++++++++++++++++++++++++++++ > src/ipa/libipa/gamma.h | 94 +++++++++++++++++ > src/ipa/libipa/meson.build | 2 + > 3 files changed, 353 insertions(+) > > diff --git a/src/ipa/libipa/gamma.cpp b/src/ipa/libipa/gamma.cpp > new file mode 100644 > index 0000000000000000000000000000000000000000..5fec5329a50f0b3dd0e39504dfbb63484ca4fcd9 > --- /dev/null > +++ b/src/ipa/libipa/gamma.cpp > @@ -0,0 +1,257 @@ > [...] > +/** > + * \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; Is there not a risk of dropping frame 0? I suppose as long as the request sequence number is used, this is guaranteed to work. > + > + 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; > +} > [...] > diff --git a/src/ipa/libipa/gamma.h b/src/ipa/libipa/gamma.h > new file mode 100644 > index 0000000000000000000000000000000000000000..94be8c7a2328e7b5a3618b0d65989ee511cece09 > --- /dev/null > +++ b/src/ipa/libipa/gamma.h > @@ -0,0 +1,94 @@ > +/* 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> Should template arguments be always camel case? I suppose this is a question for the style guide. > +class GammaAlgorithm : public GammaAlgorithmBase > +{ > +public: > + GammaAlgorithm() > + : GammaAlgorithmBase(nLutNodes) > + { > + } > + > + template<typename T> > + void prepare(gamma::FrameContext &context, Span<T> lut) This could be `Span<T, nLutNodes>` to enforce the size. Feels like a worthwhile change to me, even if creating a spans becomes a bit bit more convoluted. > + { > + 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/gamma.cpp b/src/ipa/libipa/gamma.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5fec5329a50f0b3dd0e39504dfbb63484ca4fcd9 --- /dev/null +++ b/src/ipa/libipa/gamma.cpp @@ -0,0 +1,257 @@ +/* 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 + */ + +/** + * \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 \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, + 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 gamma LUT sampling points + */ + +/** + * \var GammaAlgorithmBase::defaultGamma_ + * \brief The default gamma parameter + */ + +/** + * \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 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 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..94be8c7a2328e7b5a3618b0d65989ee511cece09 --- /dev/null +++ b/src/ipa/libipa/gamma.h @@ -0,0 +1,94 @@ +/* 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 */ 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',