[PATCH v3 09/16] libcamera: ipa: add Soft IPA
Kieran Bingham
kieran.bingham at ideasonboard.com
Tue Feb 20 16:53:19 CET 2024
Quoting Andrei Konovalov (2024-02-20 15:23:02)
> On 20.02.2024 16:05, Kieran Bingham wrote:
> > Quoting Hans de Goede (2024-02-14 17:01:13)
> >> From: Andrey Konovalov <andrey.konovalov at linaro.org>
> >>
> >> Define the Soft IPA main and event interfaces, add the Soft IPA
> >> implementation.
> >>
> >> The current src/ipa/meson.build assumes the IPA name to match the
> >> pipeline name. For this reason "-Dipas=simple" is used for the
> >> Soft IPA module.
> >>
> >> Auto exposure/gain and AWB implementation by Dennis, Toon and Martti.
> >>
> >> Auto exposure/gain targets a Mean Sample Value of 2.5 following
> >> the MSV calculation algorithm from:
> >> https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
> >>
> >> Tested-by: Bryan O'Donoghue <bryan.odonoghue at linaro.org> # sc8280xp Lenovo x13s
> >> Tested-by: Pavel Machek <pavel at ucw.cz>
> >> Reviewed-by: Pavel Machek <pavel at ucw.cz>
> >> Signed-off-by: Andrey Konovalov <andrey.konovalov at linaro.org>
> >> Co-developed-by: Dennis Bonke <admin at dennisbonke.com>
> >> Signed-off-by: Dennis Bonke <admin at dennisbonke.com>
> >> Co-developed-by: Marttico <g.martti at gmail.com>
> >> Signed-off-by: Marttico <g.martti at gmail.com>
> >> Co-developed-by: Toon Langendam <t.langendam at gmail.com>
> >> Signed-off-by: Toon Langendam <t.langendam at gmail.com>
> >> Signed-off-by: Hans de Goede <hdegoede at redhat.com>
> >> ---
> >> Changes in v3:
> >> - Use new SwIspStats::kYHistogramSize constexpr and adjust
> >> the auto-exposure/-gain code so that it can deal with
> >> that having a different value then 16 (modify the loop
> >> to divide the histogram in 5 bins to not have hardcoded
> >> values)
> >> - Rename a few foo_bar symbols to fooBar
> >> ---
> >> Documentation/Doxyfile.in | 1 +
> >> include/libcamera/ipa/meson.build | 1 +
> >> include/libcamera/ipa/soft.mojom | 28 +++
> >> meson_options.txt | 2 +-
> >> src/ipa/simple/data/meson.build | 9 +
> >> src/ipa/simple/data/soft.conf | 3 +
> >> src/ipa/simple/meson.build | 25 +++
> >> src/ipa/simple/soft_simple.cpp | 308 ++++++++++++++++++++++++++++++
> >> 8 files changed, 376 insertions(+), 1 deletion(-)
> >> create mode 100644 include/libcamera/ipa/soft.mojom
> >> create mode 100644 src/ipa/simple/data/meson.build
> >> create mode 100644 src/ipa/simple/data/soft.conf
> >> create mode 100644 src/ipa/simple/meson.build
> >> create mode 100644 src/ipa/simple/soft_simple.cpp
> >>
> >> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
> >> index a86ea6c1..2be8d47b 100644
> >> --- a/Documentation/Doxyfile.in
> >> +++ b/Documentation/Doxyfile.in
> >> @@ -44,6 +44,7 @@ EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \
> >> @TOP_SRCDIR@/src/libcamera/pipeline/ \
> >> @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \
> >> @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \
> >> + @TOP_BUILDDIR@/include/libcamera/ipa/soft_ipa_interface.h \
> >> @TOP_BUILDDIR@/src/libcamera/proxy/
> >>
> >> EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \
> >> diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build
> >> index f3b4881c..3352d08f 100644
> >> --- a/include/libcamera/ipa/meson.build
> >> +++ b/include/libcamera/ipa/meson.build
> >> @@ -65,6 +65,7 @@ pipeline_ipa_mojom_mapping = {
> >> 'ipu3': 'ipu3.mojom',
> >> 'rkisp1': 'rkisp1.mojom',
> >> 'rpi/vc4': 'raspberrypi.mojom',
> >> + 'simple': 'soft.mojom',
> >> 'vimc': 'vimc.mojom',
> >> }
> >>
> >> diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom
> >> new file mode 100644
> >> index 00000000..c249bd75
> >> --- /dev/null
> >> +++ b/include/libcamera/ipa/soft.mojom
> >> @@ -0,0 +1,28 @@
> >> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> >> +
> >> +/*
> >> + * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry.
> >> + */
> >> +
> >> +module ipa.soft;
> >> +
> >> +import "include/libcamera/ipa/core.mojom";
> >> +
> >> +interface IPASoftInterface {
> >> + init(libcamera.IPASettings settings,
> >> + libcamera.SharedFD fdStats,
> >> + libcamera.SharedFD fdParams,
> >> + libcamera.ControlInfoMap sensorCtrlInfoMap)
> >> + => (int32 ret);
> >> + start() => (int32 ret);
> >> + stop();
> >> + configure(libcamera.ControlInfoMap sensorCtrlInfoMap)
> >> + => (int32 ret);
> >> +
> >> + [async] processStats(libcamera.ControlList sensorControls);
> >> +};
> >> +
> >> +interface IPASoftEventInterface {
> >> + setSensorControls(libcamera.ControlList sensorControls);
> >> + setIspParams(int32 dummy);
> >> +};
> >> diff --git a/meson_options.txt b/meson_options.txt
> >> index 5fdc7be8..94372e47 100644
> >> --- a/meson_options.txt
> >> +++ b/meson_options.txt
> >> @@ -27,7 +27,7 @@ option('gstreamer',
> >>
> >> option('ipas',
> >> type : 'array',
> >> - choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'vimc'],
> >> + choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'simple', 'vimc'],
> >> description : 'Select which IPA modules to build')
> >>
> >> option('lc-compliance',
> >> diff --git a/src/ipa/simple/data/meson.build b/src/ipa/simple/data/meson.build
> >> new file mode 100644
> >> index 00000000..33548cc6
> >> --- /dev/null
> >> +++ b/src/ipa/simple/data/meson.build
> >> @@ -0,0 +1,9 @@
> >> +# SPDX-License-Identifier: CC0-1.0
> >> +
> >> +conf_files = files([
> >> + 'soft.conf',
> >> +])
> >> +
> >> +install_data(conf_files,
> >> + install_dir : ipa_data_dir / 'soft',
> >> + install_tag : 'runtime')
> >> diff --git a/src/ipa/simple/data/soft.conf b/src/ipa/simple/data/soft.conf
> >> new file mode 100644
> >> index 00000000..0c70e7c0
> >> --- /dev/null
> >> +++ b/src/ipa/simple/data/soft.conf
> >> @@ -0,0 +1,3 @@
> >> +# SPDX-License-Identifier: LGPL-2.1-or-later
> >> +#
> >> +# Dummy configuration file for the soft IPA.
> >> diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build
> >> new file mode 100644
> >> index 00000000..3e863db7
> >> --- /dev/null
> >> +++ b/src/ipa/simple/meson.build
> >> @@ -0,0 +1,25 @@
> >> +# SPDX-License-Identifier: CC0-1.0
> >> +
> >> +ipa_name = 'ipa_soft_simple'
> >> +
> >> +mod = shared_module(ipa_name,
> >> + ['soft_simple.cpp', libcamera_generated_ipa_headers],
> >> + name_prefix : '',
> >> + include_directories : [ipa_includes, libipa_includes],
> >> + dependencies : libcamera_private,
> >> + link_with : libipa,
> >> + install : true,
> >> + install_dir : ipa_install_dir)
> >> +
> >> +if ipa_sign_module
> >> + custom_target(ipa_name + '.so.sign',
> >> + input : mod,
> >> + output : ipa_name + '.so.sign',
> >> + command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'],
> >> + install : false,
> >> + build_by_default : true)
> >> +endif
> >> +
> >> +subdir('data')
> >> +
> >> +ipa_names += ipa_name
> >> diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
> >> new file mode 100644
> >> index 00000000..f7ac02f9
> >> --- /dev/null
> >> +++ b/src/ipa/simple/soft_simple.cpp
> >> @@ -0,0 +1,308 @@
> >> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> >> +/*
> >> + * Copyright (C) 2023, Linaro Ltd
> >> + *
> >> + * soft_simple.cpp - Simple Software Image Processing Algorithm module
> >> + */
> >> +
> >> +#include <sys/mman.h>
> >> +
> >> +#include <libcamera/base/file.h>
> >> +#include <libcamera/base/log.h>
> >> +#include <libcamera/base/shared_fd.h>
> >> +
> >> +#include <libcamera/control_ids.h>
> >> +#include <libcamera/controls.h>
> >> +
> >> +#include <libcamera/ipa/ipa_interface.h>
> >> +#include <libcamera/ipa/ipa_module_info.h>
> >> +#include <libcamera/ipa/soft_ipa_interface.h>
> >> +
> >> +#include "libcamera/internal/camera_sensor.h"
> >> +#include "libcamera/internal/software_isp/debayer_params.h"
> >> +#include "libcamera/internal/software_isp/swisp_stats.h"
> >> +
> >> +#define EXPOSURE_OPTIMAL_VALUE 2.5
> >> +#define EXPOSURE_SATISFACTORY_OFFSET 0.2
> >
> > Perhaps:
> >
> > static constexpr float kExposureOptimal = 2.5;
> > static constexpr float kExposureSatisfactory = 0.2;
> >
> > But can those constants have some more comments or documentation about
> > /why/ they are optimal and satisfactory? Where do they come from? what
> > are the ranges? Anything applicable to say about them?
>
> I second the oppinion that some more comments here would be useful.
>
> To my understanding, kExposureOptimal = ExposureBinsCount / 2.0;
> (That is, the mean sample value of the histogram should be in the middle of the range.)
Yes, even better - if they are the result of other parameters,
expressing that is far more helpful to know that things will change if
we add more bins, or ... that they'll automatically adapt...
> And kExposureSatisfactory is the hesteresys to prevent the exposure from wobbling around
> the optimal exposure. Some empirically selected value small enough to have the exposure
> close to the optimal, and big enough to prevent the jitter.
Sounds feasible to me - but wasn't clear just from seeing two numbers,
so comments welcome ;-)
> Please correct me if I am wrong.
>
> Still here is what is not quite clear to me, and the paper84final.pdf doesn't seem to
> explain:
>
> SwIspStats::kYHistogramSize is 16. For AE 5 bins are used.
> What is the advantage of reassembling the 16 bins from SwIspStats into 5
> (vs e.g. setting ExposureBinsCount to SwIspStats::kYHistogramSize and using 16 bins) ?
>
> >> +
> >> +namespace libcamera {
> >> +
> >> +LOG_DEFINE_CATEGORY(IPASoft)
> >> +
> >> +namespace ipa::soft {
> >> +
> >> +class IPASoftSimple : public ipa::soft::IPASoftInterface
> >> +{
> >> +public:
> >> + IPASoftSimple()
> >> + : ignore_updates_(0)
> >> + {
> >> + }
> >> +
> >> + ~IPASoftSimple()
> >> + {
> >> + if (stats_)
> >> + munmap(stats_, sizeof(SwIspStats));
> >> + if (params_)
> >> + munmap(params_, sizeof(DebayerParams));
> >> + }
> >> +
> >> + int init(const IPASettings &settings,
> >> + const SharedFD &fdStats,
> >> + const SharedFD &fdParams,
> >> + const ControlInfoMap &sensorInfoMap) override;
> >> + int configure(const ControlInfoMap &sensorInfoMap) override;
> >> +
> >> + int start() override;
> >> + void stop() override;
> >> +
> >> + void processStats(const ControlList &sensorControls) override;
> >> +
> >> +private:
> >> + void updateExposure(double exposureMSV);
> >> +
> >> + SharedFD fdStats_;
> >> + SharedFD fdParams_;
> >> + DebayerParams *params_;
> >> + SwIspStats *stats_;
> >
> > I'd throw a blank line between class instances and the following PODs
> >
> >> + int exposure_min_, exposure_max_;
> >> + int again_min_, again_max_;
> >> + int again_, exposure_;
> >> + int ignore_updates_;
> >
> > Should those by unsigned? Can they ever be 0 or less than zero?
>
> unsigned would be fine
>
> >> +};
> >> +
> >> +int IPASoftSimple::init([[maybe_unused]] const IPASettings &settings,
> >> + const SharedFD &fdStats,
> >> + const SharedFD &fdParams,
> >> + const ControlInfoMap &sensorInfoMap)
> >> +{
> >> + fdStats_ = std::move(fdStats);
> >> + if (!fdStats_.isValid()) {
> >> + LOG(IPASoft, Error) << "Invalid Statistics handle";
> >> + return -ENODEV;
> >> + }
> >> +
> >> + fdParams_ = std::move(fdParams);
> >> + if (!fdParams_.isValid()) {
> >> + LOG(IPASoft, Error) << "Invalid Parameters handle";
> >> + return -ENODEV;
> >> + }
> >> +
> >> + params_ = static_cast<DebayerParams *>(mmap(nullptr, sizeof(DebayerParams),
> >> + PROT_WRITE, MAP_SHARED,
> >> + fdParams_.get(), 0));
> >
> > We'd usually use MappedFrameBuffer() here so that the mappings are
> > handled by RAII here
>
> OK, will fix that.
>
> >> + if (!params_) {
> >> + LOG(IPASoft, Error) << "Unable to map Parameters";
> >> + return -ENODEV;
> >> + }
> >> +
> >> + stats_ = static_cast<SwIspStats *>(mmap(nullptr, sizeof(SwIspStats),
> >> + PROT_READ, MAP_SHARED,
> >> + fdStats_.get(), 0));
> >> + if (!stats_) {
> >> + LOG(IPASoft, Error) << "Unable to map Statistics";
> >> + return -ENODEV;
> >> + }
> >
> > Same here.
>
> Ack
>
> >> +
> >> + if (sensorInfoMap.find(V4L2_CID_EXPOSURE) == sensorInfoMap.end()) {
> >> + LOG(IPASoft, Error) << "Don't have exposure control";
> >> + return -EINVAL;
> >> + }
> >> +
> >> + if (sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN) == sensorInfoMap.end()) {
> >> + LOG(IPASoft, Error) << "Don't have gain control";
> >> + return -EINVAL;
> >> + }
> >> +
> >
> > In the future I would expect AEGC to be broken out to it's own modular
> > 'Algorithm' (see rkisp1 for the createAlgorithms()
>
> Thanks, I'll take a look.
If it makes sense and helps starting to make things modular then it's
definitely a nice to have .... but as we don't really have a lot of
algorithms here to break out - I don't mind if it's done later. It's
just helpful to be aware of the design goals of the ipas.
> > We're also lacking any set up or registration for control handling. I
> > guess for now there may be none, but do you plan to have controls? I'm
> > fine with those being added later.
>
> I'd stick to what we currently have (none), and revisit this later.
Ack. That's one part that I think breaking out algorithms to components
will help though as each algorithm can/should then be responsible for
registering what controls it will support and handling them.
>
> >> + return 0;
> >> +}
> >> +
> >> +int IPASoftSimple::configure(const ControlInfoMap &sensorInfoMap)
> >> +{
> >> + const ControlInfo &exposure_info = sensorInfoMap.find(V4L2_CID_EXPOSURE)->second;
> >> + const ControlInfo &gain_info = sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN)->second;
> >> +
> >> + exposure_min_ = exposure_info.min().get<int>();
> >> + if (!exposure_min_) {
> >> + LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear";
> >> + exposure_min_ = 1;
> >> + }
> >> + exposure_max_ = exposure_info.max().get<int>();
> >> + again_min_ = gain_info.min().get<int>();
> >> + if (!again_min_) {
> >> + LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear";
> >> + again_min_ = 100;
> >> + }
> >
> > This is where you should almost certainly be looking at the
> > CameraSensorHelpers. We expect the gain model to be abstracted by each
> > sensor through the helpers.
> >
> > IPA algorithms should operate on a linear model, and the
> > CameraSensorHelpers will convert any logarithmic gain codes to linear on
> > a per-sensor basis, as well as impose any sensor specific limits.
>
> I am going to draft a patch to use the CameraSensorHelpers before the end of this week
> (hopefully sooner).
I hope this part shouldn't be too complicated. They're already used in
other IPAs. The idea is to factor out the common sensor specific
parts away from the IPA.
--
Kieran
>
> >> + again_max_ = gain_info.max().get<int>();
> >> +
> >> + LOG(IPASoft, Info) << "Exposure " << exposure_min_ << "-" << exposure_max_
> >> + << ", gain " << again_min_ << "-" << again_max_;
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +int IPASoftSimple::start()
> >> +{
> >> + return 0;
> >> +}
> >> +
> >> +void IPASoftSimple::stop()
> >> +{
> >> +}
> >> +
> >> +void IPASoftSimple::processStats(const ControlList &sensorControls)
> >> +{
> >> + /*
> >> + * Calculate red and blue gains for AWB.
> >> + * Clamp max gain at 4.0, this also avoids 0 division.
> >> + */
> >> + if (stats_->sumR_ <= stats_->sumG_ / 4)
> >> + params_->gainR = 1024;
> >> + else
> >> + params_->gainR = 256 * stats_->sumG_ / stats_->sumR_;
> >> +
> >> + if (stats_->sumB_ <= stats_->sumG_ / 4)
> >> + params_->gainB = 1024;
> >> + else
> >> + params_->gainB = 256 * stats_->sumG_ / stats_->sumB_;
> >> +
> >> + /* Green gain and gamma values are fixed */
> >> + params_->gainG = 256;
> >> + params_->gamma = 0.5;
> >> +
> >> + setIspParams.emit(0);
> >> +
> >> + /*
> >> + * AE / AGC, use 2 frames delay to make sure that the exposure and
> >> + * the gain set have applied to the camera sensor.
> >> + */
> >
> > Using known sensor specific delays defined in the CameraSensorHelper and
> > DelayedControls should be considered here in the future. But as a first
> > implementation for CPU only, where doing /less/ work is helpful - this
> > is fine for me.
>
> Agreed.
>
> >> + if (ignore_updates_ > 0) {
> >> + --ignore_updates_;
> >> + return;
> >> + }
> >> +
> >> + /*
> >> + * Calculate Mean Sample Value (MSV) according to formula from:
> >> + * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
> >> + */
> >> + constexpr unsigned int ExposureBinsCount = 5;
> >> + constexpr unsigned int yHistValsPerBin =
> >> + SwIspStats::kYHistogramSize / ExposureBinsCount;
> >> + constexpr unsigned int yHistValsPerBinMod =
> >> + SwIspStats::kYHistogramSize /
> >> + (SwIspStats::kYHistogramSize % ExposureBinsCount + 1);
> >> + int ExposureBins[ExposureBinsCount] = {};
> >> + unsigned int denom = 0;
> >> + unsigned int num = 0;
> >> +
> >> + for (unsigned int i = 0; i < SwIspStats::kYHistogramSize; i++) {
> >> + unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
> >> + ExposureBins[idx] += stats_->yHistogram[i];
> >> + }
> >> +
> >> + for (unsigned int i = 0; i < ExposureBinsCount; i++) {
> >> + LOG(IPASoft, Debug) << i << ": " << ExposureBins[i];
> >> + denom += ExposureBins[i];
> >> + num += ExposureBins[i] * (i + 1);
> >> + }
> >> +
> >> + float exposureMSV = (float)num / denom;
> >> +
> >> + /* sanity check */
> >> + if (!sensorControls.contains(V4L2_CID_EXPOSURE) ||
> >> + !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) {
> >> + LOG(IPASoft, Error) << "Control(s) missing";
> >> + return;
> >> + }
> >> +
> >> + ControlList ctrls(sensorControls);
> >> +
> >> + exposure_ = ctrls.get(V4L2_CID_EXPOSURE).get<int>();
> >> + again_ = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get<int>();
> >> +
> >> + updateExposure(exposureMSV);
> >> +
> >> + ctrls.set(V4L2_CID_EXPOSURE, exposure_);
> >> + ctrls.set(V4L2_CID_ANALOGUE_GAIN, again_);
> >> +
> >> + ignore_updates_ = 2;
> >> +
> >> + setSensorControls.emit(ctrls);
> >> +
> >> + LOG(IPASoft, Debug) << "exposureMSV " << exposureMSV
> >> + << " exp " << exposure_ << " again " << again_
> >> + << " gain R/B " << params_->gainR << "/" << params_->gainB;
> >> +}
> >> +
> >> +/* DENOMINATOR of 10 gives ~10% increment/decrement; DENOMINATOR of 5 - about ~20% */
> >> +#define DENOMINATOR 10
> >> +#define UP_NUMERATOR (DENOMINATOR + 1)
> >> +#define DOWN_NUMERATOR (DENOMINATOR - 1)
> >
> > Our C++ code style for constants would be more like:
> >
> > static constexpr uint8_t kExpDenominator = 10;
> > static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
> > static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
>
> This was written by me, and I'll update this part to use static constexpr.
>
> >> +
> >> +void IPASoftSimple::updateExposure(double exposureMSV)
> >> +{
> >> + int next;
> >> +
> >> + if (exposureMSV < EXPOSURE_OPTIMAL_VALUE - EXPOSURE_SATISFACTORY_OFFSET) {
> >> + next = exposure_ * UP_NUMERATOR / DENOMINATOR;
> >> + if (next - exposure_ < 1)
> >> + exposure_ += 1;
> >> + else
> >> + exposure_ = next;
> >> + if (exposure_ >= exposure_max_) {
> >> + next = again_ * UP_NUMERATOR / DENOMINATOR;
> >> + if (next - again_ < 1)
> >> + again_ += 1;
> >> + else
> >> + again_ = next;
> >> + }
> >> + }
> >> +
> >> + if (exposureMSV > EXPOSURE_OPTIMAL_VALUE + EXPOSURE_SATISFACTORY_OFFSET) {
> >> + if (exposure_ == exposure_max_ && again_ != again_min_) {
> >> + next = again_ * DOWN_NUMERATOR / DENOMINATOR;
> >> + if (again_ - next < 1)
> >> + again_ -= 1;
> >> + else
> >> + again_ = next;
> >> + } else {
> >> + next = exposure_ * DOWN_NUMERATOR / DENOMINATOR;
> >> + if (exposure_ - next < 1)
> >> + exposure_ -= 1;
> >> + else
> >> + exposure_ = next;
> >> + }
> >> + }
> >> +
> >> + if (exposure_ > exposure_max_)
> >> + exposure_ = exposure_max_;
> >> + else if (exposure_ < exposure_min_)
> >> + exposure_ = exposure_min_;
> >> +
> >> + if (again_ > again_max_)
> >> + again_ = again_max_;
> >> + else if (again_ < again_min_)
> >> + again_ = again_min_;
> >
> > How about
> > exposure_ = std::clamp(exposure_, exposure_min_, exposure_max_);
> > again_ = std::clamp(again_, again_min_, again_max_);
> >
> > ?
>
> OK, will fix.
>
> Thanks,
> Andrei
>
> >> +}
> >> +
> >> +} /* namespace ipa::soft */
> >> +
> >> +/*
> >> + * External IPA module interface
> >> + */
> >> +extern "C" {
> >> +const struct IPAModuleInfo ipaModuleInfo = {
> >> + IPA_MODULE_API_VERSION,
> >> + 0,
> >> + "SimplePipelineHandler",
> >> + "simple",
> >> +};
> >> +
> >> +IPAInterface *ipaCreate()
> >> +{
> >> + return new ipa::soft::IPASoftSimple();
> >> +}
> >> +
> >> +} /* extern "C" */
> >> +
> >> +} /* namespace libcamera */
> >> --
> >> 2.43.0
> >>
More information about the libcamera-devel
mailing list