summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/camera_capabilities.cpp2
-rw-r--r--src/apps/cam/camera_session.cpp11
-rw-r--r--src/gstreamer/gstlibcamerasrc.cpp5
-rw-r--r--src/ipa/ipu3/meson.build5
-rw-r--r--src/ipa/libipa/agc_mean_luminance.cpp10
-rw-r--r--src/ipa/libipa/agc_mean_luminance.h6
-rw-r--r--src/ipa/libipa/camera_sensor_helper.cpp36
-rw-r--r--src/ipa/libipa/matrix.cpp149
-rw-r--r--src/ipa/libipa/matrix.h204
-rw-r--r--src/ipa/libipa/matrix_interpolator.cpp110
-rw-r--r--src/ipa/libipa/matrix_interpolator.h122
-rw-r--r--src/ipa/libipa/meson.build12
-rw-r--r--src/ipa/libipa/pwl.cpp459
-rw-r--r--src/ipa/libipa/pwl.h88
-rw-r--r--src/ipa/libipa/vector.cpp168
-rw-r--r--src/ipa/libipa/vector.h219
-rw-r--r--src/ipa/rkisp1/algorithms/agc.cpp166
-rw-r--r--src/ipa/rkisp1/algorithms/agc.h6
-rw-r--r--src/ipa/rkisp1/algorithms/ccm.cpp147
-rw-r--r--src/ipa/rkisp1/algorithms/ccm.h49
-rw-r--r--src/ipa/rkisp1/algorithms/cproc.cpp59
-rw-r--r--src/ipa/rkisp1/algorithms/cproc.h3
-rw-r--r--src/ipa/rkisp1/algorithms/goc.cpp148
-rw-r--r--src/ipa/rkisp1/algorithms/goc.h42
-rw-r--r--src/ipa/rkisp1/algorithms/meson.build2
-rw-r--r--src/ipa/rkisp1/ipa_context.cpp82
-rw-r--r--src/ipa/rkisp1/ipa_context.h26
-rw-r--r--src/ipa/rkisp1/meson.build6
-rw-r--r--src/ipa/rkisp1/rkisp1.cpp6
-rw-r--r--src/ipa/rkisp1/utils.cpp42
-rw-r--r--src/ipa/rkisp1/utils.h66
-rw-r--r--src/ipa/rpi/common/ipa_base.cpp7
-rw-r--r--src/ipa/rpi/common/ipa_base.h5
-rw-r--r--src/ipa/rpi/controller/cac_status.h2
-rw-r--r--src/ipa/rpi/controller/contrast_status.h4
-rw-r--r--src/ipa/rpi/controller/meson.build2
-rw-r--r--src/ipa/rpi/controller/pwl.cpp269
-rw-r--r--src/ipa/rpi/controller/pwl.h127
-rw-r--r--src/ipa/rpi/controller/rpi/af.cpp4
-rw-r--r--src/ipa/rpi/controller/rpi/af.h5
-rw-r--r--src/ipa/rpi/controller/rpi/agc_channel.cpp13
-rw-r--r--src/ipa/rpi/controller/rpi/agc_channel.h7
-rw-r--r--src/ipa/rpi/controller/rpi/awb.cpp85
-rw-r--r--src/ipa/rpi/controller/rpi/awb.h23
-rw-r--r--src/ipa/rpi/controller/rpi/ccm.cpp8
-rw-r--r--src/ipa/rpi/controller/rpi/ccm.h5
-rw-r--r--src/ipa/rpi/controller/rpi/contrast.cpp16
-rw-r--r--src/ipa/rpi/controller/rpi/contrast.h5
-rw-r--r--src/ipa/rpi/controller/rpi/geq.cpp9
-rw-r--r--src/ipa/rpi/controller/rpi/geq.h4
-rw-r--r--src/ipa/rpi/controller/rpi/hdr.cpp8
-rw-r--r--src/ipa/rpi/controller/rpi/hdr.h9
-rw-r--r--src/ipa/rpi/controller/rpi/tonemap.cpp2
-rw-r--r--src/ipa/rpi/controller/rpi/tonemap.h5
-rw-r--r--src/ipa/rpi/controller/tonemap_status.h4
-rw-r--r--src/ipa/rpi/vc4/meson.build4
-rw-r--r--src/ipa/simple/black_level.cpp2
-rw-r--r--src/ipa/simple/black_level.h5
-rw-r--r--src/ipa/simple/meson.build5
-rw-r--r--src/ipa/simple/soft_simple.cpp75
-rw-r--r--src/ipa/vimc/meson.build5
-rw-r--r--src/libcamera/control_ids_core.yaml7
-rw-r--r--src/libcamera/dma_buf_allocator.cpp246
-rw-r--r--src/libcamera/dma_heaps.cpp165
-rw-r--r--src/libcamera/meson.build2
-rw-r--r--src/libcamera/pipeline/rpi/common/pipeline_base.cpp2
-rw-r--r--src/libcamera/pipeline/rpi/vc4/vc4.cpp4
-rw-r--r--src/libcamera/pipeline/simple/simple.cpp3
-rw-r--r--src/libcamera/pipeline/vimc/vimc.cpp47
-rw-r--r--src/libcamera/software_isp/TODO13
-rw-r--r--src/libcamera/software_isp/debayer.cpp28
-rw-r--r--src/libcamera/software_isp/debayer_cpu.cpp118
-rw-r--r--src/libcamera/software_isp/debayer_cpu.h21
-rw-r--r--src/libcamera/software_isp/software_isp.cpp28
-rw-r--r--src/libcamera/v4l2_subdevice.cpp122
-rw-r--r--src/libcamera/yaml_parser.cpp94
-rw-r--r--src/v4l2/v4l2_compat.cpp30
77 files changed, 3186 insertions, 924 deletions
diff --git a/src/android/camera_capabilities.cpp b/src/android/camera_capabilities.cpp
index 6f4d48de..71043e12 100644
--- a/src/android/camera_capabilities.cpp
+++ b/src/android/camera_capabilities.cpp
@@ -1081,7 +1081,7 @@ int CameraCapabilities::initializeStaticMetadata()
}
{
- const Span<const Rectangle> &rects =
+ const Span<const Rectangle> rects =
properties.get(properties::PixelArrayActiveAreas).value_or(Span<const Rectangle>{});
std::vector<int32_t> data{
static_cast<int32_t>(rects[0].x),
diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp
index f13355ba..097dc479 100644
--- a/src/apps/cam/camera_session.cpp
+++ b/src/apps/cam/camera_session.cpp
@@ -39,9 +39,14 @@ CameraSession::CameraSession(CameraManager *cm,
{
char *endptr;
unsigned long index = strtoul(cameraId.c_str(), &endptr, 10);
- if (*endptr == '\0' && index > 0 && index <= cm->cameras().size())
- camera_ = cm->cameras()[index - 1];
- else
+
+ if (*endptr == '\0' && index > 0) {
+ auto cameras = cm->cameras();
+ if (index <= cameras.size())
+ camera_ = cameras[index - 1];
+ }
+
+ if (!camera_)
camera_ = cm->get(cameraId);
if (!camera_) {
diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
index 6a95b6af..9680d809 100644
--- a/src/gstreamer/gstlibcamerasrc.cpp
+++ b/src/gstreamer/gstlibcamerasrc.cpp
@@ -385,13 +385,14 @@ gst_libcamera_src_open(GstLibcameraSrc *self)
return false;
}
} else {
- if (cm->cameras().empty()) {
+ auto cameras = cm->cameras();
+ if (cameras.empty()) {
GST_ELEMENT_ERROR(self, RESOURCE, NOT_FOUND,
("Could not find any supported camera on this system."),
("libcamera::CameraMananger::cameras() is empty"));
return false;
}
- cam = cm->cameras()[0];
+ cam = cameras[0];
}
GST_INFO_OBJECT(self, "Using camera '%s'", cam->id().c_str());
diff --git a/src/ipa/ipu3/meson.build b/src/ipa/ipu3/meson.build
index 66c39843..e76f97c0 100644
--- a/src/ipa/ipu3/meson.build
+++ b/src/ipa/ipu3/meson.build
@@ -15,9 +15,8 @@ ipu3_ipa_sources += ipu3_ipa_algorithms
mod = shared_module(ipa_name,
[ipu3_ipa_sources, libcamera_generated_ipa_headers],
name_prefix : '',
- include_directories : [ipa_includes, libipa_includes],
- dependencies : libcamera_private,
- link_with : libipa,
+ include_directories : [ipa_includes],
+ dependencies : [libcamera_private, libipa_dep],
install : true,
install_dir : ipa_install_dir)
diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp
index 271b5ae4..f97ef117 100644
--- a/src/ipa/libipa/agc_mean_luminance.cpp
+++ b/src/ipa/libipa/agc_mean_luminance.cpp
@@ -59,9 +59,9 @@ static constexpr double kDefaultRelativeLuminanceTarget = 0.16;
/**
* \enum AgcMeanLuminance::AgcConstraint::Bound
* \brief Specify whether the constraint defines a lower or upper bound
- * \var AgcMeanLuminance::AgcConstraint::lower
+ * \var AgcMeanLuminance::AgcConstraint::Lower
* \brief The constraint defines a lower bound
- * \var AgcMeanLuminance::AgcConstraint::upper
+ * \var AgcMeanLuminance::AgcConstraint::Upper
* \brief The constraint defines an upper bound
*/
@@ -209,7 +209,7 @@ int AgcMeanLuminance::parseConstraintModes(const YamlObject &tuningData)
*/
if (constraintModes_.empty()) {
AgcConstraint constraint = {
- AgcConstraint::Bound::lower,
+ AgcConstraint::Bound::Lower,
0.98,
1.0,
0.5
@@ -467,11 +467,11 @@ double AgcMeanLuminance::constraintClampGain(uint32_t constraintModeIndex,
double newGain = constraint.yTarget * hist.bins() /
hist.interQuantileMean(constraint.qLo, constraint.qHi);
- if (constraint.bound == AgcConstraint::Bound::lower &&
+ if (constraint.bound == AgcConstraint::Bound::Lower &&
newGain > gain)
gain = newGain;
- if (constraint.bound == AgcConstraint::Bound::upper &&
+ if (constraint.bound == AgcConstraint::Bound::Upper &&
newGain < gain)
gain = newGain;
}
diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h
index 0a81c6d2..576d28be 100644
--- a/src/ipa/libipa/agc_mean_luminance.h
+++ b/src/ipa/libipa/agc_mean_luminance.h
@@ -12,6 +12,8 @@
#include <tuple>
#include <vector>
+#include <libcamera/base/utils.h>
+
#include <libcamera/controls.h>
#include "libcamera/internal/yaml_parser.h"
@@ -31,8 +33,8 @@ public:
struct AgcConstraint {
enum class Bound {
- lower = 0,
- upper = 1
+ Lower = 0,
+ Upper = 1
};
Bound bound;
double qLo;
diff --git a/src/ipa/libipa/camera_sensor_helper.cpp b/src/ipa/libipa/camera_sensor_helper.cpp
index 2cd61fcc..782ff990 100644
--- a/src/ipa/libipa/camera_sensor_helper.cpp
+++ b/src/ipa/libipa/camera_sensor_helper.cpp
@@ -369,30 +369,26 @@ static constexpr double expGainDb(double step)
class CameraSensorHelperAr0521 : public CameraSensorHelper
{
public:
- uint32_t gainCode(double gain) const override;
- double gain(uint32_t gainCode) const override;
-
-private:
- static constexpr double kStep_ = 16;
-};
-
-uint32_t CameraSensorHelperAr0521::gainCode(double gain) const
-{
- gain = std::clamp(gain, 1.0, 15.5);
- unsigned int coarse = std::log2(gain);
- unsigned int fine = (gain / (1 << coarse) - 1) * kStep_;
+ uint32_t gainCode(double gain) const override
+ {
+ gain = std::clamp(gain, 1.0, 15.5);
+ unsigned int coarse = std::log2(gain);
+ unsigned int fine = (gain / (1 << coarse) - 1) * kStep_;
- return (coarse << 4) | (fine & 0xf);
-}
+ return (coarse << 4) | (fine & 0xf);
+ }
-double CameraSensorHelperAr0521::gain(uint32_t gainCode) const
-{
- unsigned int coarse = gainCode >> 4;
- unsigned int fine = gainCode & 0xf;
+ double gain(uint32_t gainCode) const override
+ {
+ unsigned int coarse = gainCode >> 4;
+ unsigned int fine = gainCode & 0xf;
- return (1 << coarse) * (1 + fine / kStep_);
-}
+ return (1 << coarse) * (1 + fine / kStep_);
+ }
+private:
+ static constexpr double kStep_ = 16;
+};
REGISTER_CAMERA_SENSOR_HELPER("ar0521", CameraSensorHelperAr0521)
class CameraSensorHelperImx219 : public CameraSensorHelper
diff --git a/src/ipa/libipa/matrix.cpp b/src/ipa/libipa/matrix.cpp
new file mode 100644
index 00000000..8346f0d3
--- /dev/null
+++ b/src/ipa/libipa/matrix.cpp
@@ -0,0 +1,149 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Matrix and related operations
+ */
+
+#include "matrix.h"
+
+#include <libcamera/base/log.h>
+
+/**
+ * \file matrix.h
+ * \brief Matrix class
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Matrix)
+
+namespace ipa {
+
+/**
+ * \class Matrix
+ * \brief Matrix class
+ * \tparam T Type of numerical values to be stored in the matrix
+ * \tparam Rows Number of rows in the matrix
+ * \tparam Cols Number of columns in the matrix
+ */
+
+/**
+ * \fn Matrix::Matrix()
+ * \brief Construct a zero matrix
+ */
+
+/**
+ * \fn Matrix::Matrix(const std::vector<T> &data)
+ * \brief Construct a matrix from supplied data
+ * \param[in] data Data from which to construct a matrix
+ *
+ * \a data is a one-dimensional vector and will be turned into a matrix in
+ * row-major order. The size of \a data must be equal to the product of the
+ * number of rows and columns of the matrix (Rows x Cols).
+ */
+
+/**
+ * \fn Matrix::identity()
+ * \brief Construct an identity matrix
+ */
+
+/**
+ * \fn Matrix::toString()
+ * \brief Assemble and return a string describing the matrix
+ * \return A string describing the matrix
+ */
+
+/**
+ * \fn Span<const T, Cols> Matrix::operator[](size_t i) const
+ * \brief Index to a row in the matrix
+ * \param[in] i Index of row to retrieve
+ *
+ * This operator[] returns a Span, which can then be indexed into again with
+ * another operator[], allowing a convenient m[i][j] to access elements of the
+ * matrix. Note that the lifetime of the Span returned by this first-level
+ * operator[] is bound to that of the Matrix itself, so it is not recommended
+ * to save the Span that is the result of this operator[].
+ *
+ * \return Row \a i from the matrix, as a Span
+ */
+
+/**
+ * \fn Matrix::operator[](size_t i)
+ * \copydoc Matrix::operator[](size_t i) const
+ */
+
+/**
+ * \fn Matrix<T, Rows, Cols> &Matrix::operator*=(U d)
+ * \brief Multiply the matrix by a scalar in-place
+ * \tparam U Type of the numerical scalar value
+ * \param d The scalar multiplier
+ * \return Product of this matrix and scalar \a d
+ */
+
+/**
+ * \fn Matrix::Matrix<U, Rows, Cols> operator*(T d, const Matrix<U, Rows, Cols> &m)
+ * \brief Multiply the matrix by a scalar
+ * \tparam T Type of the numerical scalar value
+ * \tparam U Type of numerical values in the matrix
+ * \tparam Rows Number of rows in the matrix
+ * \tparam Cols Number of columns in the matrix
+ * \param d The scalar multiplier
+ * \param m The matrix
+ * \return Product of scalar \a d and matrix \a m
+ */
+
+/**
+ * \fn Matrix::Matrix<U, Rows, Cols> operator*(const Matrix<U, Rows, Cols> &m, T d)
+ * \copydoc operator*(T d, const Matrix<U, Rows, Cols> &m)
+ */
+
+/**
+ * \fn Matrix<T, R1, C2> operator*(const Matrix<T, R1, C1> &m1, const Matrix<T, R2, C2> &m2)
+ * \brief Matrix multiplication
+ * \tparam T Type of numerical values in the matrices
+ * \tparam R1 Number of rows in the first matrix
+ * \tparam C1 Number of columns in the first matrix
+ * \tparam R2 Number of rows in the second matrix
+ * \tparam C2 Number of columns in the second matrix
+ * \param m1 Multiplicand matrix
+ * \param m2 Multiplier matrix
+ * \return Matrix product of matrices \a m1 and \a m2
+ */
+
+/**
+ * \fn Matrix<T, Rows, Cols> operator+(const Matrix<T, Rows, Cols> &m1, const Matrix<T, Rows, Cols> &m2)
+ * \brief Matrix addition
+ * \tparam T Type of numerical values in the matrices
+ * \tparam Rows Number of rows in the matrices
+ * \tparam Cols Number of columns in the matrices
+ * \param m1 Summand matrix
+ * \param m2 Summand matrix
+ * \return Matrix sum of matrices \a m1 and \a m2
+ */
+
+#ifndef __DOXYGEN__
+/*
+ * The YAML data shall be a list of numerical values. Its size shall be equal
+ * to the product of the number of rows and columns of the matrix (Rows x
+ * Cols). The values shall be stored in row-major order.
+ */
+bool matrixValidateYaml(const YamlObject &obj, unsigned int size)
+{
+ if (!obj.isList())
+ return false;
+
+ if (obj.size() != size) {
+ LOG(Matrix, Error)
+ << "Wrong number of values in matrix: expected "
+ << size << ", got " << obj.size();
+ return false;
+ }
+
+ return true;
+}
+#endif /* __DOXYGEN__ */
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/matrix.h b/src/ipa/libipa/matrix.h
new file mode 100644
index 00000000..8aa8f343
--- /dev/null
+++ b/src/ipa/libipa/matrix.h
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Matrix and related operations
+ */
+#pragma once
+
+#include <algorithm>
+#include <cmath>
+#include <sstream>
+#include <vector>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/span.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(Matrix)
+
+namespace ipa {
+
+#ifndef __DOXYGEN__
+template<typename T, unsigned int Rows, unsigned int Cols,
+ std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
+#else
+template<typename T, unsigned int Rows, unsigned int Cols>
+#endif /* __DOXYGEN__ */
+class Matrix
+{
+public:
+ Matrix()
+ {
+ data_.fill(static_cast<T>(0));
+ }
+
+ Matrix(const std::vector<T> &data)
+ {
+ std::copy(data.begin(), data.end(), data_.begin());
+ }
+
+ static Matrix identity()
+ {
+ Matrix ret;
+ for (size_t i = 0; i < std::min(Rows, Cols); i++)
+ ret[i][i] = static_cast<T>(1);
+ return ret;
+ }
+
+ ~Matrix() = default;
+
+ const std::string toString() const
+ {
+ std::stringstream out;
+
+ out << "Matrix { ";
+ for (unsigned int i = 0; i < Rows; i++) {
+ out << "[ ";
+ for (unsigned int j = 0; j < Cols; j++) {
+ out << (*this)[i][j];
+ out << ((j + 1 < Cols) ? ", " : " ");
+ }
+ out << ((i + 1 < Rows) ? "], " : "]");
+ }
+ out << " }";
+
+ return out.str();
+ }
+
+ Span<const T, Cols> operator[](size_t i) const
+ {
+ return Span<const T, Cols>{ &data_.data()[i * Cols], Cols };
+ }
+
+ Span<T, Cols> operator[](size_t i)
+ {
+ return Span<T, Cols>{ &data_.data()[i * Cols], Cols };
+ }
+
+#ifndef __DOXYGEN__
+ template<typename U, std::enable_if_t<std::is_arithmetic_v<U>>>
+#else
+ template<typename U>
+#endif /* __DOXYGEN__ */
+ Matrix<T, Rows, Cols> &operator*=(U d)
+ {
+ for (unsigned int i = 0; i < Rows * Cols; i++)
+ data_[i] *= d;
+ return *this;
+ }
+
+private:
+ std::array<T, Rows * Cols> data_;
+};
+
+#ifndef __DOXYGEN__
+template<typename T, typename U, unsigned int Rows, unsigned int Cols,
+ std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
+#else
+template<typename T, typename U, unsigned int Rows, unsigned int Cols>
+#endif /* __DOXYGEN__ */
+Matrix<U, Rows, Cols> operator*(T d, const Matrix<U, Rows, Cols> &m)
+{
+ Matrix<U, Rows, Cols> result;
+
+ for (unsigned int i = 0; i < Rows; i++) {
+ for (unsigned int j = 0; j < Cols; j++)
+ result[i][j] = d * m[i][j];
+ }
+
+ return result;
+}
+
+#ifndef __DOXYGEN__
+template<typename T, typename U, unsigned int Rows, unsigned int Cols,
+ std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
+#else
+template<typename T, typename U, unsigned int Rows, unsigned int Cols>
+#endif /* __DOXYGEN__ */
+Matrix<U, Rows, Cols> operator*(const Matrix<U, Rows, Cols> &m, T d)
+{
+ return d * m;
+}
+
+#ifndef __DOXYGEN__
+template<typename T,
+ unsigned int R1, unsigned int C1,
+ unsigned int R2, unsigned int C2,
+ std::enable_if_t<C1 == R2> * = nullptr>
+#else
+template<typename T, unsigned int R1, unsigned int C1, unsigned int R2, unsigned in C2>
+#endif /* __DOXYGEN__ */
+Matrix<T, R1, C2> operator*(const Matrix<T, R1, C1> &m1, const Matrix<T, R2, C2> &m2)
+{
+ Matrix<T, R1, C2> result;
+
+ for (unsigned int i = 0; i < R1; i++) {
+ for (unsigned int j = 0; j < C2; j++) {
+ T sum = 0;
+
+ for (unsigned int k = 0; k < C1; k++)
+ sum += m1[i][k] * m2[k][j];
+
+ result[i][j] = sum;
+ }
+ }
+
+ return result;
+}
+
+template<typename T, unsigned int Rows, unsigned int Cols>
+Matrix<T, Rows, Cols> operator+(const Matrix<T, Rows, Cols> &m1, const Matrix<T, Rows, Cols> &m2)
+{
+ Matrix<T, Rows, Cols> result;
+
+ for (unsigned int i = 0; i < Rows; i++) {
+ for (unsigned int j = 0; j < Cols; j++)
+ result[i][j] = m1[i][j] + m2[i][j];
+ }
+
+ return result;
+}
+
+#ifndef __DOXYGEN__
+bool matrixValidateYaml(const YamlObject &obj, unsigned int size);
+#endif /* __DOXYGEN__ */
+
+} /* namespace ipa */
+
+#ifndef __DOXYGEN__
+template<typename T, unsigned int Rows, unsigned int Cols>
+std::ostream &operator<<(std::ostream &out, const ipa::Matrix<T, Rows, Cols> &m)
+{
+ out << m.toString();
+ return out;
+}
+
+template<typename T, unsigned int Rows, unsigned int Cols>
+struct YamlObject::Getter<ipa::Matrix<T, Rows, Cols>> {
+ std::optional<ipa::Matrix<T, Rows, Cols>> get(const YamlObject &obj) const
+ {
+ if (!ipa::matrixValidateYaml(obj, Rows * Cols))
+ return std::nullopt;
+
+ ipa::Matrix<T, Rows, Cols> matrix;
+ T *data = &matrix[0][0];
+
+ unsigned int i = 0;
+ for (const YamlObject &entry : obj.asList()) {
+ const auto value = entry.get<T>();
+ if (!value)
+ return std::nullopt;
+
+ data[i++] = *value;
+ }
+
+ return matrix;
+ }
+};
+#endif /* __DOXYGEN__ */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/matrix_interpolator.cpp b/src/ipa/libipa/matrix_interpolator.cpp
new file mode 100644
index 00000000..04ca177f
--- /dev/null
+++ b/src/ipa/libipa/matrix_interpolator.cpp
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Helper class for interpolating maps of matrices
+ */
+#include "matrix_interpolator.h"
+
+#include <algorithm>
+#include <string>
+
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "matrix.h"
+
+/**
+ * \file matrix_interpolator.h
+ * \brief Helper class for interpolating maps of matrices
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(MatrixInterpolator)
+
+namespace ipa {
+
+/**
+ * \class MatrixInterpolator
+ * \brief Class for storing, retrieving, and interpolating matrices
+ * \tparam T Type of numerical values to be stored in the matrices
+ * \tparam R Number of rows in the matrices
+ * \tparam C Number of columns in the matrices
+ *
+ * The main use case is to pass a map from color temperatures to corresponding
+ * matrices (eg. color correction), and then requesting a matrix for a specific
+ * color temperature. This class will abstract away the interpolation portion.
+ */
+
+/**
+ * \fn MatrixInterpolator::MatrixInterpolator(const std::map<unsigned int, Matrix<T, R, C>> &matrices)
+ * \brief Construct a matrix interpolator from a map of matrices
+ * \param matrices Map from which to construct the matrix interpolator
+ */
+
+/**
+ * \fn MatrixInterpolator::reset()
+ * \brief Reset the matrix interpolator content to a single identity matrix
+ */
+
+/**
+ * \fn int MatrixInterpolator<T, R, C>::readYaml()
+ * \brief Initialize an MatrixInterpolator instance from yaml
+ * \tparam T Type of data stored in the matrices
+ * \tparam R Number of rows of the matrices
+ * \tparam C Number of columns of the matrices
+ * \param[in] yaml The yaml object that contains the map of unsigned integers to matrices
+ * \param[in] key_name The name of the key in the yaml object
+ * \param[in] matrix_name The name of the matrix in the yaml object
+ *
+ * The yaml object is expected to be a list of maps. Each map has two or more
+ * pairs: one of \a key_name to the key value (usually color temperature), and
+ * one or more of \a matrix_name to the matrix. This is a bit difficult to
+ * explain, so here is an example (in python, as it is easier to parse than
+ * yaml):
+ * [
+ * {
+ * 'ct': 2860,
+ * 'ccm': [ 2.12089, -0.52461, -0.59629,
+ * -0.85342, 2.80445, -0.95103,
+ * -0.26897, -1.14788, 2.41685 ],
+ * 'offsets': [ 0, 0, 0 ]
+ * },
+ *
+ * {
+ * 'ct': 2960,
+ * 'ccm': [ 2.26962, -0.54174, -0.72789,
+ * -0.77008, 2.60271, -0.83262,
+ * -0.26036, -1.51254, 2.77289 ],
+ * 'offsets': [ 0, 0, 0 ]
+ * },
+ *
+ * {
+ * 'ct': 3603,
+ * 'ccm': [ 2.18644, -0.66148, -0.52496,
+ * -0.77828, 2.69474, -0.91645,
+ * -0.25239, -0.83059, 2.08298 ],
+ * 'offsets': [ 0, 0, 0 ]
+ * },
+ * ]
+ *
+ * In this case, \a key_name would be 'ct', and \a matrix_name can be either
+ * 'ccm' or 'offsets'. This way multiple matrix interpolators can be defined in
+ * one set of color temperature ranges in the tuning file, and they can be
+ * retrieved separately with the \a matrix_name parameter.
+ *
+ * \return Zero on success, negative error code otherwise
+ */
+
+/**
+ * \fn Matrix<T, R, C> MatrixInterpolator<T, R, C>::get(unsigned int key)
+ * \brief Retrieve a matrix from the list of matrices, interpolating if necessary
+ * \param[in] key The unsigned integer key of the matrix to retrieve
+ * \return The matrix corresponding to the color temperature
+ */
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/matrix_interpolator.h b/src/ipa/libipa/matrix_interpolator.h
new file mode 100644
index 00000000..087c4fd1
--- /dev/null
+++ b/src/ipa/libipa/matrix_interpolator.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Helper class for interpolating maps of matrices
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <tuple>
+
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "matrix.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(MatrixInterpolator)
+
+namespace ipa {
+
+#ifndef __DOXYGEN__
+template<typename T, unsigned int R, unsigned int C,
+ std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
+#else
+template<typename T, unsigned int R, unsigned int C>
+#endif /* __DOXYGEN__ */
+class MatrixInterpolator
+{
+public:
+ MatrixInterpolator()
+ {
+ reset();
+ }
+
+ MatrixInterpolator(const std::map<unsigned int, Matrix<T, R, C>> &matrices)
+ {
+ for (const auto &pair : matrices)
+ matrices_[pair.first] = pair.second;
+ }
+
+ ~MatrixInterpolator() {}
+
+ void reset()
+ {
+ matrices_.clear();
+ matrices_[0] = Matrix<T, R, C>::identity();
+ }
+
+ int readYaml(const libcamera::YamlObject &yaml,
+ const std::string &key_name,
+ const std::string &matrix_name)
+ {
+ matrices_.clear();
+
+ if (!yaml.isList()) {
+ LOG(MatrixInterpolator, Error) << "yaml object must be a list";
+ return -EINVAL;
+ }
+
+ for (const auto &value : yaml.asList()) {
+ unsigned int ct = std::stoul(value[key_name].get<std::string>(""));
+ std::optional<Matrix<T, R, C>> matrix =
+ value[matrix_name].get<Matrix<T, R, C>>();
+ if (!matrix) {
+ LOG(MatrixInterpolator, Error) << "Failed to read matrix";
+ return -EINVAL;
+ }
+
+ matrices_[ct] = *matrix;
+
+ LOG(MatrixInterpolator, Debug)
+ << "Read matrix '" << matrix_name << "' for key '"
+ << key_name << "' " << ct << ": "
+ << matrices_[ct].toString();
+ }
+
+ if (matrices_.size() < 1) {
+ LOG(MatrixInterpolator, Error) << "Need at least one matrix";
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ Matrix<T, R, C> get(unsigned int ct)
+ {
+ ASSERT(matrices_.size() > 0);
+
+ if (matrices_.size() == 1 ||
+ ct <= matrices_.begin()->first)
+ return matrices_.begin()->second;
+
+ if (ct >= matrices_.rbegin()->first)
+ return matrices_.rbegin()->second;
+
+ if (matrices_.find(ct) != matrices_.end())
+ return matrices_[ct];
+
+ /* The above four guarantee that this will succeed */
+ auto iter = matrices_.upper_bound(ct);
+ unsigned int ctUpper = iter->first;
+ unsigned int ctLower = (--iter)->first;
+
+ double lambda = (ct - ctLower) / static_cast<double>(ctUpper - ctLower);
+ Matrix<T, R, C> ret =
+ lambda * matrices_[ctUpper] + (1.0 - lambda) * matrices_[ctLower];
+ return ret;
+ }
+
+private:
+ std::map<unsigned int, Matrix<T, R, C>> matrices_;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build
index 7ce885da..eff8ce26 100644
--- a/src/ipa/libipa/meson.build
+++ b/src/ipa/libipa/meson.build
@@ -7,7 +7,11 @@ libipa_headers = files([
'exposure_mode_helper.h',
'fc_queue.h',
'histogram.h',
+ 'matrix.h',
+ 'matrix_interpolator.h',
'module.h',
+ 'pwl.h',
+ 'vector.h',
])
libipa_sources = files([
@@ -17,7 +21,11 @@ libipa_sources = files([
'exposure_mode_helper.cpp',
'fc_queue.cpp',
'histogram.cpp',
+ 'matrix.cpp',
+ 'matrix_interpolator.cpp',
'module.cpp',
+ 'pwl.cpp',
+ 'vector.cpp',
])
libipa_includes = include_directories('..')
@@ -25,3 +33,7 @@ libipa_includes = include_directories('..')
libipa = static_library('ipa', [libipa_sources, libipa_headers],
include_directories : ipa_includes,
dependencies : libcamera_private)
+
+libipa_dep = declare_dependency(sources : libipa_headers,
+ include_directories : libipa_includes,
+ link_with : libipa)
diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp
new file mode 100644
index 00000000..9b213754
--- /dev/null
+++ b/src/ipa/libipa/pwl.cpp
@@ -0,0 +1,459 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * Piecewise linear functions
+ */
+
+#include "pwl.h"
+
+#include <assert.h>
+#include <cmath>
+#include <sstream>
+#include <stdexcept>
+
+/**
+ * \file pwl.h
+ * \brief Piecewise linear functions
+ */
+
+namespace libcamera {
+
+namespace ipa {
+
+/**
+ * \class Pwl
+ * \brief Describe a univariate piecewise linear function in two-dimensional
+ * real space
+ *
+ * A piecewise linear function is a univariate function that maps reals to
+ * reals, and it is composed of multiple straight-line segments.
+ *
+ * While a mathematical piecewise linear function would usually be defined by
+ * a list of linear functions and for which values of the domain they apply,
+ * this Pwl class is instead defined by a list of points at which these line
+ * segments intersect. These intersecting points are known as knots.
+ *
+ * https://en.wikipedia.org/wiki/Piecewise_linear_function
+ *
+ * A consequence of the Pwl class being defined by knots instead of linear
+ * functions is that the values of the piecewise linear function past the ends
+ * of the function are constants as opposed to linear functions. In a
+ * mathematical piecewise linear function that is defined by multiple linear
+ * functions, the ends of the function are also linear functions and hence grow
+ * to infinity (or negative infinity). However, since this Pwl class is defined
+ * by knots, the y-value of the leftmost and rightmost knots will hold for all
+ * x values to negative infinity and positive infinity, respectively.
+ */
+
+/**
+ * \typedef Pwl::Point
+ * \brief Describe a point in two-dimensional real space
+ */
+
+/**
+ * \class Pwl::Interval
+ * \brief Describe an interval in one-dimensional real space
+ */
+
+/**
+ * \fn Pwl::Interval::Interval(double _start, double _end)
+ * \brief Construct an interval
+ * \param[in] _start Start of the interval
+ * \param[in] _end End of the interval
+ */
+
+/**
+ * \fn Pwl::Interval::contains
+ * \brief Check if a given value falls within the interval
+ * \param[in] value Value to check
+ * \return True if the value falls within the interval, including its bounds,
+ * or false otherwise
+ */
+
+/**
+ * \fn Pwl::Interval::clamp
+ * \brief Clamp a value such that it is within the interval
+ * \param[in] value Value to clamp
+ * \return The clamped value
+ */
+
+/**
+ * \fn Pwl::Interval::length
+ * \brief Compute the length of the interval
+ * \return The length of the interval
+ */
+
+/**
+ * \var Pwl::Interval::start
+ * \brief Start of the interval
+ */
+
+/**
+ * \var Pwl::Interval::end
+ * \brief End of the interval
+ */
+
+/**
+ * \brief Construct an empty piecewise linear function
+ */
+Pwl::Pwl()
+{
+}
+
+/**
+ * \brief Construct a piecewise linear function from a list of 2D points
+ * \param[in] points Vector of points from which to construct the piecewise
+ * linear function
+ *
+ * \a points must be in ascending order of x-value.
+ */
+Pwl::Pwl(const std::vector<Point> &points)
+ : points_(points)
+{
+}
+
+/**
+ * \copydoc Pwl::Pwl(const std::vector<Point> &points)
+ *
+ * The contents of the \a points vector is moved to the newly constructed Pwl
+ * instance.
+ */
+Pwl::Pwl(std::vector<Point> &&points)
+ : points_(std::move(points))
+{
+}
+
+/**
+ * \brief Append a point to the end of the piecewise linear function
+ * \param[in] x x-coordinate of the point to add to the piecewise linear function
+ * \param[in] y y-coordinate of the point to add to the piecewise linear function
+ * \param[in] eps Epsilon for the minimum x distance between points (optional)
+ *
+ * The point's x-coordinate must be greater than the x-coordinate of the last
+ * (= greatest) point already in the piecewise linear function.
+ */
+void Pwl::append(double x, double y, const double eps)
+{
+ if (points_.empty() || points_.back().x() + eps < x)
+ points_.push_back(Point({ x, y }));
+}
+
+/**
+ * \brief Prepend a point to the beginning of the piecewise linear function
+ * \param[in] x x-coordinate of the point to add to the piecewise linear function
+ * \param[in] y y-coordinate of the point to add to the piecewise linear function
+ * \param[in] eps Epsilon for the minimum x distance between points (optional)
+ *
+ * The point's x-coordinate must be less than the x-coordinate of the first
+ * (= smallest) point already in the piecewise linear function.
+ */
+void Pwl::prepend(double x, double y, const double eps)
+{
+ if (points_.empty() || points_.front().x() - eps > x)
+ points_.insert(points_.begin(), Point({ x, y }));
+}
+
+/**
+ * \fn Pwl::empty() const
+ * \brief Check if the piecewise linear function is empty
+ * \return True if there are no points in the function, false otherwise
+ */
+
+/**
+ * \fn Pwl::size() const
+ * \brief Retrieve the number of points in the piecewise linear function
+ * \return The number of points in the piecewise linear function
+ */
+
+/**
+ * \brief Get the domain of the piecewise linear function
+ * \return An interval representing the domain
+ */
+Pwl::Interval Pwl::domain() const
+{
+ return Interval(points_[0].x(), points_[points_.size() - 1].x());
+}
+
+/**
+ * \brief Get the range of the piecewise linear function
+ * \return An interval representing the range
+ */
+Pwl::Interval Pwl::range() const
+{
+ double lo = points_[0].y(), hi = lo;
+ for (auto &p : points_)
+ lo = std::min(lo, p.y()), hi = std::max(hi, p.y());
+ return Interval(lo, hi);
+}
+
+/**
+ * \brief Evaluate the piecewise linear function
+ * \param[in] x The x value to input into the function
+ * \param[inout] span Initial guess for span
+ * \param[in] updateSpan Set to true to update span
+ *
+ * Evaluate Pwl, optionally supplying an initial guess for the
+ * "span". The "span" may be optionally be updated. If you want to know
+ * the "span" value but don't have an initial guess you can set it to
+ * -1.
+ *
+ * \return The result of evaluating the piecewise linear function at position \a x
+ */
+double Pwl::eval(double x, int *span, bool updateSpan) const
+{
+ int index = findSpan(x, span && *span != -1
+ ? *span
+ : points_.size() / 2 - 1);
+ if (span && updateSpan)
+ *span = index;
+ return points_[index].y() +
+ (x - points_[index].x()) * (points_[index + 1].y() - points_[index].y()) /
+ (points_[index + 1].x() - points_[index].x());
+}
+
+int Pwl::findSpan(double x, int span) const
+{
+ /*
+ * Pwls are generally small, so linear search may well be faster than
+ * binary, though could review this if large Pwls start turning up.
+ */
+ int lastSpan = points_.size() - 2;
+ /*
+ * some algorithms may call us with span pointing directly at the last
+ * control point
+ */
+ span = std::max(0, std::min(lastSpan, span));
+ while (span < lastSpan && x >= points_[span + 1].x())
+ span++;
+ while (span && x < points_[span].x())
+ span--;
+ return span;
+}
+
+/**
+ * \brief Compute the inverse function
+ * \param[in] eps Epsilon for the minimum x distance between points (optional)
+ *
+ * The output includes whether the resulting inverse function is a proper
+ * (true) inverse, or only a best effort (e.g. input was non-monotonic).
+ *
+ * \return A pair of the inverse piecewise linear function, and whether or not
+ * the result is a proper/true inverse
+ */
+std::pair<Pwl, bool> Pwl::inverse(const double eps) const
+{
+ bool appended = false, prepended = false, neither = false;
+ Pwl inverse;
+
+ for (Point const &p : points_) {
+ if (inverse.empty()) {
+ inverse.append(p.y(), p.x(), eps);
+ } else if (std::abs(inverse.points_.back().x() - p.y()) <= eps ||
+ std::abs(inverse.points_.front().x() - p.y()) <= eps) {
+ /* do nothing */;
+ } else if (p.y() > inverse.points_.back().x()) {
+ inverse.append(p.y(), p.x(), eps);
+ appended = true;
+ } else if (p.y() < inverse.points_.front().x()) {
+ inverse.prepend(p.y(), p.x(), eps);
+ prepended = true;
+ } else {
+ neither = true;
+ }
+ }
+
+ /*
+ * This is not a proper inverse if we found ourselves putting points
+ * onto both ends of the inverse, or if there were points that couldn't
+ * go on either.
+ */
+ bool trueInverse = !(neither || (appended && prepended));
+
+ return { inverse, trueInverse };
+}
+
+/**
+ * \brief Compose two piecewise linear functions together
+ * \param[in] other The "other" piecewise linear function
+ * \param[in] eps Epsilon for the minimum x distance between points (optional)
+ *
+ * The "this" function is done first, and "other" after.
+ *
+ * \return The composed piecewise linear function
+ */
+Pwl Pwl::compose(Pwl const &other, const double eps) const
+{
+ double thisX = points_[0].x(), thisY = points_[0].y();
+ int thisSpan = 0, otherSpan = other.findSpan(thisY, 0);
+ Pwl result({ Point({ thisX, other.eval(thisY, &otherSpan, false) }) });
+
+ while (thisSpan != (int)points_.size() - 1) {
+ double dx = points_[thisSpan + 1].x() - points_[thisSpan].x(),
+ dy = points_[thisSpan + 1].y() - points_[thisSpan].y();
+ if (std::abs(dy) > eps &&
+ otherSpan + 1 < (int)other.points_.size() &&
+ points_[thisSpan + 1].y() >= other.points_[otherSpan + 1].x() + eps) {
+ /*
+ * next control point in result will be where this
+ * function's y reaches the next span in other
+ */
+ thisX = points_[thisSpan].x() +
+ (other.points_[otherSpan + 1].x() -
+ points_[thisSpan].y()) *
+ dx / dy;
+ thisY = other.points_[++otherSpan].x();
+ } else if (std::abs(dy) > eps && otherSpan > 0 &&
+ points_[thisSpan + 1].y() <=
+ other.points_[otherSpan - 1].x() - eps) {
+ /*
+ * next control point in result will be where this
+ * function's y reaches the previous span in other
+ */
+ thisX = points_[thisSpan].x() +
+ (other.points_[otherSpan + 1].x() -
+ points_[thisSpan].y()) *
+ dx / dy;
+ thisY = other.points_[--otherSpan].x();
+ } else {
+ /* we stay in the same span in other */
+ thisSpan++;
+ thisX = points_[thisSpan].x(),
+ thisY = points_[thisSpan].y();
+ }
+ result.append(thisX, other.eval(thisY, &otherSpan, false),
+ eps);
+ }
+ return result;
+}
+
+/**
+ * \brief Apply function to (x, y) values at every control point
+ * \param[in] f Function to be applied
+ */
+void Pwl::map(std::function<void(double x, double y)> f) const
+{
+ for (auto &pt : points_)
+ f(pt.x(), pt.y());
+}
+
+/**
+ * \brief Apply function to (x, y0, y1) values wherever either Pwl has a
+ * control point.
+ * \param[in] pwl0 First piecewise linear function
+ * \param[in] pwl1 Second piecewise linear function
+ * \param[in] f Function to be applied
+ *
+ * This applies the function \a f to every parameter (x, y0, y1), where x is
+ * the combined list of x-values from \a pwl0 and \a pwl1, y0 is the y-value
+ * for the given x in \a pwl0, and y1 is the y-value for the same x in \a pwl1.
+ */
+void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1,
+ std::function<void(double x, double y0, double y1)> f)
+{
+ int span0 = 0, span1 = 0;
+ double x = std::min(pwl0.points_[0].x(), pwl1.points_[0].x());
+ f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));
+
+ while (span0 < (int)pwl0.points_.size() - 1 ||
+ span1 < (int)pwl1.points_.size() - 1) {
+ if (span0 == (int)pwl0.points_.size() - 1)
+ x = pwl1.points_[++span1].x();
+ else if (span1 == (int)pwl1.points_.size() - 1)
+ x = pwl0.points_[++span0].x();
+ else if (pwl0.points_[span0 + 1].x() > pwl1.points_[span1 + 1].x())
+ x = pwl1.points_[++span1].x();
+ else
+ x = pwl0.points_[++span0].x();
+ f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));
+ }
+}
+
+/**
+ * \brief Combine two Pwls
+ * \param[in] pwl0 First piecewise linear function
+ * \param[in] pwl1 Second piecewise linear function
+ * \param[in] f Function to be applied
+ * \param[in] eps Epsilon for the minimum x distance between points (optional)
+ *
+ * Create a new Pwl where the y values are given by running \a f wherever
+ * either pwl has a knot.
+ *
+ * \return The combined pwl
+ */
+Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1,
+ std::function<double(double x, double y0, double y1)> f,
+ const double eps)
+{
+ Pwl result;
+ map2(pwl0, pwl1, [&](double x, double y0, double y1) {
+ result.append(x, f(x, y0, y1), eps);
+ });
+ return result;
+}
+
+/**
+ * \brief Multiply the piecewise linear function
+ * \param[in] d Scalar multiplier to multiply the function by
+ * \return This function, after it has been multiplied by \a d
+ */
+Pwl &Pwl::operator*=(double d)
+{
+ for (auto &pt : points_)
+ pt[1] *= d;
+ return *this;
+}
+
+/**
+ * \brief Assemble and return a string describing the piecewise linear function
+ * \return A string describing the piecewise linear function
+ */
+std::string Pwl::toString() const
+{
+ std::stringstream ss;
+ ss << "Pwl { ";
+ for (auto &p : points_)
+ ss << "(" << p.x() << ", " << p.y() << ") ";
+ ss << "}";
+ return ss.str();
+}
+
+} /* namespace ipa */
+
+#ifndef __DOXYGEN__
+/*
+ * The YAML data shall be a list of numerical values with an even number of
+ * elements. They are parsed in pairs into x and y points in the piecewise
+ * linear function, and added in order. x must be monotonically increasing.
+ */
+template<>
+std::optional<ipa::Pwl>
+YamlObject::Getter<ipa::Pwl>::get(const YamlObject &obj) const
+{
+ if (!obj.size() || obj.size() % 2)
+ return std::nullopt;
+
+ ipa::Pwl pwl;
+
+ const auto &list = obj.asList();
+
+ for (auto it = list.begin(); it != list.end(); it++) {
+ auto x = it->get<double>();
+ if (!x)
+ return std::nullopt;
+ auto y = (++it)->get<double>();
+ if (!y)
+ return std::nullopt;
+
+ pwl.append(*x, *y);
+ }
+
+ if (pwl.size() != obj.size() / 2)
+ return std::nullopt;
+
+ return pwl;
+}
+#endif /* __DOXYGEN__ */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/pwl.h b/src/ipa/libipa/pwl.h
new file mode 100644
index 00000000..b6f93494
--- /dev/null
+++ b/src/ipa/libipa/pwl.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2019, Raspberry Pi Ltd
+ *
+ * Piecewise linear functions interface
+ */
+#pragma once
+
+#include <algorithm>
+#include <cmath>
+#include <functional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "vector.h"
+
+namespace libcamera {
+
+namespace ipa {
+
+class Pwl
+{
+public:
+ using Point = Vector<double, 2>;
+
+ struct Interval {
+ Interval(double _start, double _end)
+ : start(_start), end(_end) {}
+
+ bool contains(double value)
+ {
+ return value >= start && value <= end;
+ }
+
+ double clamp(double value)
+ {
+ return std::clamp(value, start, end);
+ }
+
+ double length() const { return end - start; }
+
+ double start, end;
+ };
+
+ Pwl();
+ Pwl(const std::vector<Point> &points);
+ Pwl(std::vector<Point> &&points);
+
+ void append(double x, double y, double eps = 1e-6);
+
+ bool empty() const { return points_.empty(); }
+ size_t size() const { return points_.size(); }
+
+ Interval domain() const;
+ Interval range() const;
+
+ double eval(double x, int *span = nullptr,
+ bool updateSpan = true) const;
+
+ std::pair<Pwl, bool> inverse(double eps = 1e-6) const;
+ Pwl compose(const Pwl &other, double eps = 1e-6) const;
+
+ void map(std::function<void(double x, double y)> f) const;
+
+ static Pwl
+ combine(const Pwl &pwl0, const Pwl &pwl1,
+ std::function<double(double x, double y0, double y1)> f,
+ double eps = 1e-6);
+
+ Pwl &operator*=(double d);
+
+ std::string toString() const;
+
+private:
+ static void map2(const Pwl &pwl0, const Pwl &pwl1,
+ std::function<void(double x, double y0, double y1)> f);
+ void prepend(double x, double y, double eps = 1e-6);
+ int findSpan(double x, int span) const;
+
+ std::vector<Point> points_;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/vector.cpp b/src/ipa/libipa/vector.cpp
new file mode 100644
index 00000000..bd00b019
--- /dev/null
+++ b/src/ipa/libipa/vector.cpp
@@ -0,0 +1,168 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Vector and related operations
+ */
+
+#include "vector.h"
+
+#include <libcamera/base/log.h>
+
+/**
+ * \file vector.h
+ * \brief Vector class
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Vector)
+
+namespace ipa {
+
+/**
+ * \class Vector
+ * \brief Vector class
+ * \tparam T Type of numerical values to be stored in the vector
+ * \tparam Rows Number of dimension of the vector (= number of elements)
+ */
+
+/**
+ * \fn Vector::Vector()
+ * \brief Construct a zero vector
+ */
+
+/**
+ * \fn Vector::Vector(const std::array<T, Rows> &data)
+ * \brief Construct vector from supplied data
+ * \param data Data from which to construct a vector
+ *
+ * The size of \a data must be equal to the dimension size Rows of the vector.
+ */
+
+/**
+ * \fn T Vector::operator[](size_t i) const
+ * \brief Index to an element in the vector
+ * \param i Index of element to retrieve
+ * \return Element at index \a i from the vector
+ */
+
+/**
+ * \fn T &Vector::operator[](size_t i)
+ * \copydoc Vector::operator[](size_t i) const
+ */
+
+/**
+ * \fn Vector::x()
+ * \brief Convenience function to access the first element of the vector
+ * \return The first element of the vector
+ */
+
+/**
+ * \fn Vector::y()
+ * \brief Convenience function to access the second element of the vector
+ * \return The second element of the vector
+ */
+
+/**
+ * \fn Vector::z()
+ * \brief Convenience function to access the third element of the vector
+ * \return The third element of the vector
+ */
+
+/**
+ * \fn Vector::operator-() const
+ * \brief Negate a Vector by negating both all of its coordinates
+ * \return The negated vector
+ */
+
+/**
+ * \fn Vector::operator-(Vector const &other) const
+ * \brief Subtract one vector from another
+ * \param[in] other The other vector
+ * \return The difference of \a other from this vector
+ */
+
+/**
+ * \fn Vector::operator+()
+ * \brief Add two vectors together
+ * \param[in] other The other vector
+ * \return The sum of the two vectors
+ */
+
+/**
+ * \fn Vector::operator*(const Vector<T, Rows> &other) const
+ * \brief Compute the dot product
+ * \param[in] other The other vector
+ * \return The dot product of the two vectors
+ */
+
+/**
+ * \fn Vector::operator*(T factor) const
+ * \brief Multiply the vector by a scalar
+ * \param[in] factor The factor
+ * \return The vector multiplied by \a factor
+ */
+
+/**
+ * \fn Vector::operator/()
+ * \brief Divide the vector by a scalar
+ * \param[in] factor The factor
+ * \return The vector divided by \a factor
+ */
+
+/**
+ * \fn Vector::length2()
+ * \brief Get the squared length of the vector
+ * \return The squared length of the vector
+ */
+
+/**
+ * \fn Vector::length()
+ * \brief Get the length of the vector
+ * \return The length of the vector
+ */
+
+/**
+ * \fn Vector<T, Rows> operator*(const Matrix<T, Rows, Cols> &m, const Vector<T, Cols> &v)
+ * \brief Multiply a matrix by a vector
+ * \tparam T Numerical type of the contents of the matrix and vector
+ * \tparam Rows The number of rows in the matrix
+ * \tparam Cols The number of columns in the matrix (= rows in the vector)
+ * \param m The matrix
+ * \param v The vector
+ * \return Product of matrix \a m and vector \a v
+ */
+
+/**
+ * \fn bool operator==(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs)
+ * \brief Compare vectors for equality
+ * \return True if the two vectors are equal, false otherwise
+ */
+
+/**
+ * \fn bool operator!=(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs)
+ * \brief Compare vectors for inequality
+ * \return True if the two vectors are not equal, false otherwise
+ */
+
+#ifndef __DOXYGEN__
+bool vectorValidateYaml(const YamlObject &obj, unsigned int size)
+{
+ if (!obj.isList())
+ return false;
+
+ if (obj.size() != size) {
+ LOG(Vector, Error)
+ << "Wrong number of values in YAML vector: expected "
+ << size << ", got " << obj.size();
+ return false;
+ }
+
+ return true;
+}
+#endif /* __DOXYGEN__ */
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/vector.h b/src/ipa/libipa/vector.h
new file mode 100644
index 00000000..556e0967
--- /dev/null
+++ b/src/ipa/libipa/vector.h
@@ -0,0 +1,219 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Vector and related operations
+ */
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <cmath>
+#include <sstream>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/span.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "matrix.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(Vector)
+
+namespace ipa {
+
+#ifndef __DOXYGEN__
+template<typename T, unsigned int Rows,
+ std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
+#else
+template<typename T, unsigned int Rows>
+#endif /* __DOXYGEN__ */
+class Vector
+{
+public:
+ constexpr Vector() = default;
+
+ constexpr Vector(const std::array<T, Rows> &data)
+ {
+ for (unsigned int i = 0; i < Rows; i++)
+ data_[i] = data[i];
+ }
+
+ const T &operator[](size_t i) const
+ {
+ ASSERT(i < data_.size());
+ return data_[i];
+ }
+
+ T &operator[](size_t i)
+ {
+ ASSERT(i < data_.size());
+ return data_[i];
+ }
+
+#ifndef __DOXYGEN__
+ template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
+#endif /* __DOXYGEN__ */
+ constexpr T x() const
+ {
+ return data_[0];
+ }
+
+#ifndef __DOXYGEN__
+ template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
+#endif /* __DOXYGEN__ */
+ constexpr T y() const
+ {
+ return data_[1];
+ }
+
+#ifndef __DOXYGEN__
+ template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
+#endif /* __DOXYGEN__ */
+ constexpr T z() const
+ {
+ return data_[2];
+ }
+
+ constexpr Vector<T, Rows> operator-() const
+ {
+ Vector<T, Rows> ret;
+ for (unsigned int i = 0; i < Rows; i++)
+ ret[i] = -data_[i];
+ return ret;
+ }
+
+ constexpr Vector<T, Rows> operator-(const Vector<T, Rows> &other) const
+ {
+ Vector<T, Rows> ret;
+ for (unsigned int i = 0; i < Rows; i++)
+ ret[i] = data_[i] - other[i];
+ return ret;
+ }
+
+ constexpr Vector<T, Rows> operator+(const Vector<T, Rows> &other) const
+ {
+ Vector<T, Rows> ret;
+ for (unsigned int i = 0; i < Rows; i++)
+ ret[i] = data_[i] + other[i];
+ return ret;
+ }
+
+ constexpr T operator*(const Vector<T, Rows> &other) const
+ {
+ T ret = 0;
+ for (unsigned int i = 0; i < Rows; i++)
+ ret += data_[i] * other[i];
+ return ret;
+ }
+
+ constexpr Vector<T, Rows> operator*(T factor) const
+ {
+ Vector<T, Rows> ret;
+ for (unsigned int i = 0; i < Rows; i++)
+ ret[i] = data_[i] * factor;
+ return ret;
+ }
+
+ constexpr Vector<T, Rows> operator/(T factor) const
+ {
+ Vector<T, Rows> ret;
+ for (unsigned int i = 0; i < Rows; i++)
+ ret[i] = data_[i] / factor;
+ return ret;
+ }
+
+ constexpr double length2() const
+ {
+ double ret = 0;
+ for (unsigned int i = 0; i < Rows; i++)
+ ret += data_[i] * data_[i];
+ return ret;
+ }
+
+ constexpr double length() const
+ {
+ return std::sqrt(length2());
+ }
+
+private:
+ std::array<T, Rows> data_;
+};
+
+template<typename T, unsigned int Rows, unsigned int Cols>
+Vector<T, Rows> operator*(const Matrix<T, Rows, Cols> &m, const Vector<T, Cols> &v)
+{
+ Vector<T, Rows> result;
+
+ for (unsigned int i = 0; i < Rows; i++) {
+ T sum = 0;
+ for (unsigned int j = 0; j < Cols; j++)
+ sum += m[i][j] * v[j];
+ result[i] = sum;
+ }
+
+ return result;
+}
+
+template<typename T, unsigned int Rows>
+bool operator==(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs)
+{
+ for (unsigned int i = 0; i < Rows; i++) {
+ if (lhs[i] != rhs[i])
+ return false;
+ }
+
+ return true;
+}
+
+template<typename T, unsigned int Rows>
+bool operator!=(const Vector<T, Rows> &lhs, const Vector<T, Rows> &rhs)
+{
+ return !(lhs == rhs);
+}
+
+#ifndef __DOXYGEN__
+bool vectorValidateYaml(const YamlObject &obj, unsigned int size);
+#endif /* __DOXYGEN__ */
+
+} /* namespace ipa */
+
+#ifndef __DOXYGEN__
+template<typename T, unsigned int Rows>
+std::ostream &operator<<(std::ostream &out, const ipa::Vector<T, Rows> &v)
+{
+ out << "Vector { ";
+ for (unsigned int i = 0; i < Rows; i++) {
+ out << v[i];
+ out << ((i + 1 < Rows) ? ", " : " ");
+ }
+ out << " }";
+
+ return out;
+}
+
+template<typename T, unsigned int Rows>
+struct YamlObject::Getter<ipa::Vector<T, Rows>> {
+ std::optional<ipa::Vector<T, Rows>> get(const YamlObject &obj) const
+ {
+ if (!ipa::vectorValidateYaml(obj, Rows))
+ return std::nullopt;
+
+ ipa::Vector<T, Rows> vector;
+
+ unsigned int i = 0;
+ for (const YamlObject &entry : obj.asList()) {
+ const auto value = entry.get<T>();
+ if (!value)
+ return std::nullopt;
+ vector[i++] = *value;
+ }
+
+ return vector;
+ }
+};
+#endif /* __DOXYGEN__ */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index 50e0690f..f12f8b60 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -10,6 +10,8 @@
#include <algorithm>
#include <chrono>
#include <cmath>
+#include <tuple>
+#include <vector>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -17,6 +19,8 @@
#include <libcamera/control_ids.h>
#include <libcamera/ipa/core_ipa_interface.h>
+#include "libcamera/internal/yaml_parser.h"
+
#include "libipa/histogram.h"
/**
@@ -36,6 +40,86 @@ namespace ipa::rkisp1::algorithms {
LOG_DEFINE_CATEGORY(RkISP1Agc)
+int Agc::parseMeteringModes(IPAContext &context, const YamlObject &tuningData)
+{
+ if (!tuningData.isDictionary())
+ LOG(RkISP1Agc, Warning)
+ << "'AeMeteringMode' parameter not found in tuning file";
+
+ for (const auto &[key, value] : tuningData.asDict()) {
+ if (controls::AeMeteringModeNameValueMap.find(key) ==
+ controls::AeMeteringModeNameValueMap.end()) {
+ LOG(RkISP1Agc, Warning)
+ << "Skipping unknown metering mode '" << key << "'";
+ continue;
+ }
+
+ std::vector<uint8_t> weights =
+ value.getList<uint8_t>().value_or(std::vector<uint8_t>{});
+ if (weights.size() != context.hw->numHistogramWeights) {
+ LOG(RkISP1Agc, Warning)
+ << "Failed to read metering mode'" << key << "'";
+ continue;
+ }
+
+ meteringModes_[controls::AeMeteringModeNameValueMap.at(key)] = weights;
+ }
+
+ if (meteringModes_.empty()) {
+ LOG(RkISP1Agc, Warning)
+ << "No metering modes read from tuning file; defaulting to matrix";
+ int32_t meteringModeId = controls::AeMeteringModeNameValueMap.at("MeteringMatrix");
+ std::vector<uint8_t> weights(context.hw->numHistogramWeights, 1);
+
+ meteringModes_[meteringModeId] = weights;
+ }
+
+ std::vector<ControlValue> meteringModes;
+ std::vector<int> meteringModeKeys = utils::map_keys(meteringModes_);
+ std::transform(meteringModeKeys.begin(), meteringModeKeys.end(),
+ std::back_inserter(meteringModes),
+ [](int x) { return ControlValue(x); });
+ context.ctrlMap[&controls::AeMeteringMode] = ControlInfo(meteringModes);
+
+ return 0;
+}
+
+uint8_t Agc::computeHistogramPredivider(const Size &size,
+ enum rkisp1_cif_isp_histogram_mode mode)
+{
+ /*
+ * The maximum number of pixels that could potentially be in one bin is
+ * if all the pixels of the image are in it, multiplied by 3 for the
+ * three color channels. The counter for each bin is 16 bits wide, so
+ * `factor` thus contains the number of times we'd wrap around. This is
+ * obviously the number of pixels that we need to skip to make sure
+ * that we don't wrap around, but we compute the square root of it
+ * instead, as the skip that we need to program is for both the x and y
+ * directions.
+ *
+ * Even though it looks like dividing into a counter of 65536 would
+ * overflow by 1, this is apparently fine according to the hardware
+ * documentation, and this successfully gets the expected documented
+ * predivider size for cases where:
+ * (width / predivider) * (height / predivider) * 3 == 65536.
+ *
+ * There's a bit of extra rounding math to make sure the rounding goes
+ * the correct direction so that the square of the step is big enough
+ * to encompass the `factor` number of pixels that we need to skip.
+ *
+ * \todo Take into account weights. That is, if the weights are low
+ * enough we can potentially reduce the predivider to increase
+ * precision. This needs some investigation however, as this hardware
+ * behavior is undocumented and is only an educated guess.
+ */
+ int count = mode == RKISP1_CIF_ISP_HISTOGRAM_MODE_RGB_COMBINED ? 3 : 1;
+ double factor = size.width * size.height * count / 65536.0;
+ double root = std::sqrt(factor);
+ uint8_t predivider = static_cast<uint8_t>(std::ceil(root));
+
+ return std::clamp<uint8_t>(predivider, 3, 127);
+}
+
Agc::Agc()
{
supportsRaw_ = true;
@@ -59,6 +143,12 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData)
if (ret)
return ret;
+ const YamlObject &yamlMeteringModes = tuningData["AeMeteringMode"];
+ ret = parseMeteringModes(context, yamlMeteringModes);
+ if (ret)
+ return ret;
+
+ context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true);
context.ctrlMap.merge(controls());
return 0;
@@ -81,8 +171,19 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
context.activeState.agc.autoEnabled = !context.configuration.raw;
- context.activeState.agc.constraintMode = constraintModes().begin()->first;
- context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first;
+ context.activeState.agc.constraintMode =
+ static_cast<controls::AeConstraintModeEnum>(constraintModes().begin()->first);
+ context.activeState.agc.exposureMode =
+ static_cast<controls::AeExposureModeEnum>(exposureModeHelpers().begin()->first);
+ context.activeState.agc.meteringMode =
+ static_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first);
+
+ /*
+ * \todo This should probably come from FrameDurationLimits instead,
+ * except it's computed in the IPA and not here so we'd have to
+ * recompute it.
+ */
+ context.activeState.agc.maxFrameDuration = context.configuration.sensor.maxShutterSpeed;
/*
* Define the measurement window for AGC as a centered rectangle
@@ -93,7 +194,6 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
context.configuration.agc.measureWindow.h_size = 3 * configInfo.outputSize.width / 4;
context.configuration.agc.measureWindow.v_size = 3 * configInfo.outputSize.height / 4;
- /* \todo Run this again when FrameDurationLimits is passed in */
setLimits(context.configuration.sensor.minShutterSpeed,
context.configuration.sensor.maxShutterSpeed,
context.configuration.sensor.minAnalogueGain,
@@ -147,6 +247,34 @@ void Agc::queueRequest(IPAContext &context,
frameContext.agc.exposure = agc.manual.exposure;
frameContext.agc.gain = agc.manual.gain;
}
+
+ const auto &meteringMode = controls.get(controls::AeMeteringMode);
+ if (meteringMode) {
+ frameContext.agc.updateMetering = agc.meteringMode != *meteringMode;
+ agc.meteringMode =
+ static_cast<controls::AeMeteringModeEnum>(*meteringMode);
+ }
+ frameContext.agc.meteringMode = agc.meteringMode;
+
+ const auto &exposureMode = controls.get(controls::AeExposureMode);
+ if (exposureMode)
+ agc.exposureMode =
+ static_cast<controls::AeExposureModeEnum>(*exposureMode);
+ frameContext.agc.exposureMode = agc.exposureMode;
+
+ const auto &constraintMode = controls.get(controls::AeConstraintMode);
+ if (constraintMode)
+ agc.constraintMode =
+ static_cast<controls::AeConstraintModeEnum>(*constraintMode);
+ frameContext.agc.constraintMode = agc.constraintMode;
+
+ const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits);
+ if (frameDurationLimits) {
+ utils::Duration maxFrameDuration =
+ std::chrono::milliseconds((*frameDurationLimits).back());
+ agc.maxFrameDuration = maxFrameDuration;
+ }
+ frameContext.agc.maxFrameDuration = agc.maxFrameDuration;
}
/**
@@ -160,7 +288,7 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
frameContext.agc.gain = context.activeState.agc.automatic.gain;
}
- if (frame > 0)
+ if (frame > 0 && !frameContext.agc.updateMetering)
return;
/* Configure the measurement window. */
@@ -178,14 +306,20 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
params->meas.hst_config.meas_window = context.configuration.agc.measureWindow;
/* Produce the luminance histogram. */
params->meas.hst_config.mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM;
+
/* Set an average weighted histogram. */
Span<uint8_t> weights{
params->meas.hst_config.hist_weight,
context.hw->numHistogramWeights
};
- std::fill(weights.begin(), weights.end(), 1);
- /* Step size can't be less than 3. */
- params->meas.hst_config.histogram_predivider = 4;
+ std::vector<uint8_t> &modeWeights = meteringModes_.at(frameContext.agc.meteringMode);
+ std::copy(modeWeights.begin(), modeWeights.end(), weights.begin());
+
+ struct rkisp1_cif_isp_window window = params->meas.hst_config.meas_window;
+ Size windowSize = { window.h_size, window.v_size };
+ params->meas.hst_config.histogram_predivider =
+ computeHistogramPredivider(windowSize,
+ static_cast<rkisp1_cif_isp_histogram_mode>(params->meas.hst_config.mode));
/* Update the configuration for histogram. */
params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_HST;
@@ -201,6 +335,7 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
* frameContext.sensor.exposure;
metadata.set(controls::AnalogueGain, frameContext.sensor.gain);
metadata.set(controls::ExposureTime, exposureTime.get<std::micro>());
+ metadata.set(controls::AeEnable, frameContext.agc.autoEnabled);
/* \todo Use VBlank value calculated from each frame exposure. */
uint32_t vTotal = context.configuration.sensor.size.height
@@ -208,6 +343,10 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
utils::Duration frameDuration = context.configuration.sensor.lineDuration
* vTotal;
metadata.set(controls::FrameDuration, frameDuration.get<std::micro>());
+
+ metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode);
+ metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode);
+ metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode);
}
/**
@@ -282,6 +421,15 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
[](uint32_t x) { return x >> 4; });
expMeans_ = { params->ae.exp_mean, context.hw->numAeCells };
+ utils::Duration maxShutterSpeed =
+ std::clamp(frameContext.agc.maxFrameDuration,
+ context.configuration.sensor.minShutterSpeed,
+ context.configuration.sensor.maxShutterSpeed);
+ setLimits(context.configuration.sensor.minShutterSpeed,
+ maxShutterSpeed,
+ context.configuration.sensor.minAnalogueGain,
+ context.configuration.sensor.maxAnalogueGain);
+
/*
* The Agc algorithm needs to know the effective exposure value that was
* applied to the sensor when the statistics were collected.
@@ -294,8 +442,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
utils::Duration shutterTime;
double aGain, dGain;
std::tie(shutterTime, aGain, dGain) =
- calculateNewEv(context.activeState.agc.constraintMode,
- context.activeState.agc.exposureMode,
+ calculateNewEv(frameContext.agc.constraintMode,
+ frameContext.agc.exposureMode,
hist, effectiveExposureValue);
LOG(RkISP1Agc, Debug)
diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
index 04b3247e..9ceaa82b 100644
--- a/src/ipa/rkisp1/algorithms/agc.h
+++ b/src/ipa/rkisp1/algorithms/agc.h
@@ -44,11 +44,17 @@ public:
ControlList &metadata) override;
private:
+ int parseMeteringModes(IPAContext &context, const YamlObject &tuningData);
+ uint8_t computeHistogramPredivider(const Size &size,
+ enum rkisp1_cif_isp_histogram_mode mode);
+
void fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
ControlList &metadata);
double estimateLuminance(double gain) const override;
Span<const uint8_t> expMeans_;
+
+ std::map<int32_t, std::vector<uint8_t>> meteringModes_;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp
new file mode 100644
index 00000000..c1f5403a
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/ccm.cpp
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * RkISP1 Color Correction Matrix control algorithm
+ */
+
+#include "ccm.h"
+
+#include <algorithm>
+#include <chrono>
+#include <cmath>
+#include <tuple>
+#include <vector>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/control_ids.h>
+
+#include <libcamera/ipa/core_ipa_interface.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "../utils.h"
+#include "libipa/matrix_interpolator.h"
+
+/**
+ * \file ccm.h
+ */
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+/**
+ * \class Ccm
+ * \brief A color correction matrix algorithm
+ */
+
+LOG_DEFINE_CATEGORY(RkISP1Ccm)
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
+{
+ int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm");
+ if (ret < 0) {
+ LOG(RkISP1Ccm, Warning)
+ << "Failed to parse 'ccm' "
+ << "parameter from tuning file; falling back to unit matrix";
+ ccm_.reset();
+ }
+
+ ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets");
+ if (ret < 0) {
+ LOG(RkISP1Ccm, Warning)
+ << "Failed to parse 'offsets' "
+ << "parameter from tuning file; falling back to zero offsets";
+ /*
+ * MatrixInterpolator::reset() resets to identity matrices
+ * while here we need zero matrices so we need to construct it
+ * ourselves.
+ */
+ Matrix<int16_t, 3, 1> m({ 0, 0, 0 });
+ std::map<unsigned int, Matrix<int16_t, 3, 1>> matrices = { { 0, m } };
+ offsets_ = MatrixInterpolator<int16_t, 3, 1>(matrices);
+ }
+
+ return 0;
+}
+
+void Ccm::setParameters(rkisp1_params_cfg *params,
+ const Matrix<float, 3, 3> &matrix,
+ const Matrix<int16_t, 3, 1> &offsets)
+{
+ struct rkisp1_cif_isp_ctk_config &config = params->others.ctk_config;
+
+ /*
+ * 4 bit integer and 7 bit fractional, ranging from -8 (0x400) to
+ * +7.992 (0x3ff)
+ */
+ for (unsigned int i = 0; i < 3; i++) {
+ for (unsigned int j = 0; j < 3; j++)
+ config.coeff[i][j] =
+ utils::floatingToFixedPoint<4, 7, uint16_t, double>(matrix[i][j]);
+ }
+
+ for (unsigned int i = 0; i < 3; i++)
+ config.ct_offset[i] = offsets[i][0] & 0xfff;
+
+ LOG(RkISP1Ccm, Debug) << "Setting matrix " << matrix;
+ LOG(RkISP1Ccm, Debug) << "Setting offsets " << offsets;
+
+ params->module_en_update |= RKISP1_CIF_ISP_MODULE_CTK;
+ params->module_ens |= RKISP1_CIF_ISP_MODULE_CTK;
+ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_CTK;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void Ccm::prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params)
+{
+ uint32_t ct = context.activeState.awb.temperatureK;
+
+ /*
+ * \todo The colour temperature will likely be noisy, add filtering to
+ * avoid updating the CCM matrix all the time.
+ */
+ if (frame > 0 && ct == ct_)
+ return;
+
+ ct_ = ct;
+ Matrix<float, 3, 3> ccm = ccm_.get(ct);
+ Matrix<int16_t, 3, 1> offsets = offsets_.get(ct);
+
+ frameContext.ccm.ccm = ccm;
+
+ setParameters(params, ccm, offsets);
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::process
+ */
+void Ccm::process([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ [[maybe_unused]] const rkisp1_stat_buffer *stats,
+ ControlList &metadata)
+{
+ float m[9];
+ for (unsigned int i = 0; i < 3; i++) {
+ for (unsigned int j = 0; j < 3; j++)
+ m[i] = frameContext.ccm.ccm[i][j];
+ }
+ metadata.set(controls::ColourCorrectionMatrix, m);
+}
+
+REGISTER_IPA_ALGORITHM(Ccm, "Ccm")
+
+} /* namespace ipa::rkisp1::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/ccm.h b/src/ipa/rkisp1/algorithms/ccm.h
new file mode 100644
index 00000000..30cb8821
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/ccm.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * RkISP1 Color Correction Matrix control algorithm
+ */
+
+#pragma once
+
+#include <linux/rkisp1-config.h>
+
+#include "libipa/matrix.h"
+#include "libipa/matrix_interpolator.h"
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+class Ccm : public Algorithm
+{
+public:
+ Ccm() {}
+ ~Ccm() = default;
+
+ int init(IPAContext &context, const YamlObject &tuningData) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const rkisp1_stat_buffer *stats,
+ ControlList &metadata) override;
+
+private:
+ void parseYaml(const YamlObject &tuningData);
+ void setParameters(rkisp1_params_cfg *params,
+ const Matrix<float, 3, 3> &matrix,
+ const Matrix<int16_t, 3, 1> &offsets);
+
+ unsigned int ct_;
+ MatrixInterpolator<float, 3, 3> ccm_;
+ MatrixInterpolator<int16_t, 3, 1> offsets_;
+};
+
+} /* namespace ipa::rkisp1::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp
index 68bb8180..ef0931b2 100644
--- a/src/ipa/rkisp1/algorithms/cproc.cpp
+++ b/src/ipa/rkisp1/algorithms/cproc.cpp
@@ -33,20 +33,71 @@ namespace ipa::rkisp1::algorithms {
LOG_DEFINE_CATEGORY(RkISP1CProc)
+namespace {
+
+constexpr float kDefaultBrightness = 0.0f;
+constexpr float kDefaultContrast = 1.0f;
+constexpr float kDefaultSaturation = 1.0f;
+
+int convertBrightness(const float v)
+{
+ return std::clamp<int>(std::lround(v * 128), -128, 127);
+}
+
+int convertContrastOrSaturation(const float v)
+{
+ return std::clamp<int>(std::lround(v * 128), 0, 255);
+}
+
+} /* namespace */
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int ColorProcessing::init(IPAContext &context,
+ [[maybe_unused]] const YamlObject &tuningData)
+{
+ auto &cmap = context.ctrlMap;
+
+ cmap[&controls::Brightness] = ControlInfo(-1.0f, 0.993f, kDefaultBrightness);
+ cmap[&controls::Contrast] = ControlInfo(0.0f, 1.993f, kDefaultContrast);
+ cmap[&controls::Saturation] = ControlInfo(0.0f, 1.993f, kDefaultSaturation);
+
+ return 0;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::configure
+ */
+int ColorProcessing::configure(IPAContext &context,
+ [[maybe_unused]] const IPACameraSensorInfo &configInfo)
+{
+ auto &cproc = context.activeState.cproc;
+
+ cproc.brightness = convertBrightness(kDefaultBrightness);
+ cproc.contrast = convertContrastOrSaturation(kDefaultContrast);
+ cproc.saturation = convertContrastOrSaturation(kDefaultSaturation);
+
+ return 0;
+}
+
/**
* \copydoc libcamera::ipa::Algorithm::queueRequest
*/
void ColorProcessing::queueRequest(IPAContext &context,
- [[maybe_unused]] const uint32_t frame,
+ const uint32_t frame,
IPAFrameContext &frameContext,
const ControlList &controls)
{
auto &cproc = context.activeState.cproc;
bool update = false;
+ if (frame == 0)
+ update = true;
+
const auto &brightness = controls.get(controls::Brightness);
if (brightness) {
- int value = std::clamp<int>(std::lround(*brightness * 128), -128, 127);
+ int value = convertBrightness(*brightness);
if (cproc.brightness != value) {
cproc.brightness = value;
update = true;
@@ -57,7 +108,7 @@ void ColorProcessing::queueRequest(IPAContext &context,
const auto &contrast = controls.get(controls::Contrast);
if (contrast) {
- int value = std::clamp<int>(std::lround(*contrast * 128), 0, 255);
+ int value = convertContrastOrSaturation(*contrast);
if (cproc.contrast != value) {
cproc.contrast = value;
update = true;
@@ -68,7 +119,7 @@ void ColorProcessing::queueRequest(IPAContext &context,
const auto saturation = controls.get(controls::Saturation);
if (saturation) {
- int value = std::clamp<int>(std::lround(*saturation * 128), 0, 255);
+ int value = convertContrastOrSaturation(*saturation);
if (cproc.saturation != value) {
cproc.saturation = value;
update = true;
diff --git a/src/ipa/rkisp1/algorithms/cproc.h b/src/ipa/rkisp1/algorithms/cproc.h
index bafba5cc..e50e7200 100644
--- a/src/ipa/rkisp1/algorithms/cproc.h
+++ b/src/ipa/rkisp1/algorithms/cproc.h
@@ -21,6 +21,9 @@ public:
ColorProcessing() = default;
~ColorProcessing() = default;
+ int init(IPAContext &context, const YamlObject &tuningData) override;
+ int configure(IPAContext &context,
+ const IPACameraSensorInfo &configInfo) override;
void queueRequest(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
const ControlList &controls) override;
diff --git a/src/ipa/rkisp1/algorithms/goc.cpp b/src/ipa/rkisp1/algorithms/goc.cpp
new file mode 100644
index 00000000..a82cee3b
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/goc.cpp
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * RkISP1 Gamma out control
+ */
+#include "goc.h"
+
+#include <cmath>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/control_ids.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "linux/rkisp1-config.h"
+
+/**
+ * \file goc.h
+ */
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+/**
+ * \class GammaOutCorrection
+ * \brief RkISP1 Gamma out correction
+ *
+ * This algorithm implements the gamma out curve for the RkISP1. It defaults to
+ * a gamma value of 2.2.
+ *
+ * As gamma is internally represented as a piecewise linear function with only
+ * 17 knots, the difference between gamma=2.2 and sRGB gamma is minimal.
+ * Therefore sRGB gamma was not implemented as special case.
+ *
+ * Useful links:
+ * - https://www.cambridgeincolour.com/tutorials/gamma-correction.htm
+ * - https://en.wikipedia.org/wiki/SRGB
+ */
+
+LOG_DEFINE_CATEGORY(RkISP1Gamma)
+
+const float kDefaultGamma = 2.2f;
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int GammaOutCorrection::init(IPAContext &context, const YamlObject &tuningData)
+{
+ if (context.hw->numGammaOutSamples !=
+ RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10) {
+ LOG(RkISP1Gamma, Error)
+ << "Gamma is not implemented for RkISP1 V12";
+ return -EINVAL;
+ }
+
+ defaultGamma_ = tuningData["gamma"].get<double>(kDefaultGamma);
+ context.ctrlMap[&controls::Gamma] = ControlInfo(0.1f, 10.0f, defaultGamma_);
+
+ return 0;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::configure
+ */
+int GammaOutCorrection::configure(IPAContext &context,
+ [[maybe_unused]] const IPACameraSensorInfo &configInfo)
+{
+ context.activeState.goc.gamma = defaultGamma_;
+ return 0;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+void GammaOutCorrection::queueRequest(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls)
+{
+ if (frame == 0)
+ frameContext.goc.update = true;
+
+ const auto &gamma = controls.get(controls::Gamma);
+ if (gamma) {
+ context.activeState.goc.gamma = *gamma;
+ frameContext.goc.update = true;
+ LOG(RkISP1Gamma, Debug) << "Set gamma to " << *gamma;
+ }
+
+ frameContext.goc.gamma = context.activeState.goc.gamma;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void GammaOutCorrection::prepare(IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params)
+{
+ ASSERT(context.hw->numGammaOutSamples ==
+ RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10);
+
+ /*
+ * The logarithmic segments as specified in the reference.
+ * Plus an additional 0 to make the loop easier
+ */
+ static constexpr std::array<unsigned int, RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10> segments = {
+ 64, 64, 64, 64, 128, 128, 128, 128, 256,
+ 256, 256, 512, 512, 512, 512, 512, 0
+ };
+ __u16 *gamma_y = params->others.goc_config.gamma_y;
+
+ if (!frameContext.goc.update)
+ return;
+
+ unsigned x = 0;
+ for (const auto [i, size] : utils::enumerate(segments)) {
+ gamma_y[i] = std::pow(x / 4096.0, 1.0 / frameContext.goc.gamma) * 1023.0;
+ x += size;
+ }
+
+ params->others.goc_config.mode = RKISP1_CIF_ISP_GOC_MODE_LOGARITHMIC;
+ params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_GOC;
+ params->module_en_update |= RKISP1_CIF_ISP_MODULE_GOC;
+ params->module_ens |= RKISP1_CIF_ISP_MODULE_GOC;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::process
+ */
+void GammaOutCorrection::process([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ IPAFrameContext &frameContext,
+ [[maybe_unused]] const rkisp1_stat_buffer *stats,
+ ControlList &metadata)
+{
+ metadata.set(controls::Gamma, frameContext.goc.gamma);
+}
+
+REGISTER_IPA_ALGORITHM(GammaOutCorrection, "GammaOutCorrection")
+
+} /* namespace ipa::rkisp1::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/goc.h b/src/ipa/rkisp1/algorithms/goc.h
new file mode 100644
index 00000000..0e05d7ce
--- /dev/null
+++ b/src/ipa/rkisp1/algorithms/goc.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * RkISP1 Gamma out control
+ */
+
+#pragma once
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::rkisp1::algorithms {
+
+class GammaOutCorrection : public Algorithm
+{
+public:
+ GammaOutCorrection() = default;
+ ~GammaOutCorrection() = default;
+
+ int init(IPAContext &context, const YamlObject &tuningData) override;
+ int configure(IPAContext &context,
+ const IPACameraSensorInfo &configInfo) override;
+ void queueRequest(IPAContext &context,
+ const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const ControlList &controls) override;
+ void prepare(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ rkisp1_params_cfg *params) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const rkisp1_stat_buffer *stats,
+ ControlList &metadata) override;
+
+private:
+ float defaultGamma_;
+};
+
+} /* namespace ipa::rkisp1::algorithms */
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/algorithms/meson.build b/src/ipa/rkisp1/algorithms/meson.build
index 93a48329..1734a667 100644
--- a/src/ipa/rkisp1/algorithms/meson.build
+++ b/src/ipa/rkisp1/algorithms/meson.build
@@ -4,10 +4,12 @@ rkisp1_ipa_algorithms = files([
'agc.cpp',
'awb.cpp',
'blc.cpp',
+ 'ccm.cpp',
'cproc.cpp',
'dpcc.cpp',
'dpf.cpp',
'filter.cpp',
+ 'goc.cpp',
'gsl.cpp',
'lsc.cpp',
])
diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp
index 283bc131..9f3f576a 100644
--- a/src/ipa/rkisp1/ipa_context.cpp
+++ b/src/ipa/rkisp1/ipa_context.cpp
@@ -137,13 +137,43 @@ namespace libcamera::ipa::rkisp1 {
* \var IPAActiveState::agc
* \brief State for the Automatic Gain Control algorithm
*
- * The exposure and gain are the latest values computed by the AGC algorithm.
+ * The \a automatic variables track the latest values computed by algorithm
+ * based on the latest processed statistics. All other variables track the
+ * consolidated controls requested in queued requests.
*
- * \var IPAActiveState::agc.exposure
- * \brief Exposure time expressed as a number of lines
+ * \struct IPAActiveState::agc.manual
+ * \brief Manual exposure time and analog gain (set through requests)
*
- * \var IPAActiveState::agc.gain
- * \brief Analogue gain multiplier
+ * \var IPAActiveState::agc.manual.exposure
+ * \brief Manual exposure time expressed as a number of lines as set by the
+ * ExposureTime control
+ *
+ * \var IPAActiveState::agc.manual.gain
+ * \brief Manual analogue gain as set by the AnalogueGain control
+ *
+ * \struct IPAActiveState::agc.automatic
+ * \brief Automatic exposure time and analog gain (computed by the algorithm)
+ *
+ * \var IPAActiveState::agc.automatic.exposure
+ * \brief Automatic exposure time expressed as a number of lines
+ *
+ * \var IPAActiveState::agc.automatic.gain
+ * \brief Automatic analogue gain multiplier
+ *
+ * \var IPAActiveState::agc.autoEnabled
+ * \brief Manual/automatic AGC state as set by the AeEnable control
+ *
+ * \var IPAActiveState::agc.constraintMode
+ * \brief Constraint mode as set by the AeConstraintMode control
+ *
+ * \var IPAActiveState::agc.exposureMode
+ * \brief Exposure mode as set by the AeExposureMode control
+ *
+ * \var IPAActiveState::agc.meteringMode
+ * \brief Metering mode as set by the AeMeteringMode control
+ *
+ * \var IPAActiveState::agc.maxFrameDuration
+ * \brief Maximum frame duration as set by the FrameDurationLimits control
*/
/**
@@ -218,6 +248,14 @@ namespace libcamera::ipa::rkisp1 {
*/
/**
+ * \var IPAActiveState::goc
+ * \brief State for the goc algorithm
+ *
+ * \var IPAActiveState::goc.gamma
+ * \brief Gamma value applied as 1.0/gamma
+ */
+
+/**
* \struct IPAFrameContext
* \brief Per-frame context for algorithms
*
@@ -257,12 +295,30 @@ namespace libcamera::ipa::rkisp1 {
* applied to the sensor in order to take effect for this frame.
*
* \var IPAFrameContext::agc.exposure
- * \brief Exposure time expressed as a number of lines
+ * \brief Exposure time expressed as a number of lines computed by the algorithm
*
* \var IPAFrameContext::agc.gain
- * \brief Analogue gain multiplier
+ * \brief Analogue gain multiplier computed by the algorithm
*
* The gain should be adapted to the sensor specific gain code before applying.
+ *
+ * \var IPAFrameContext::agc.autoEnabled
+ * \brief Manual/automatic AGC state as set by the AeEnable control
+ *
+ * \var IPAFrameContext::agc.constraintMode
+ * \brief Constraint mode as set by the AeConstraintMode control
+ *
+ * \var IPAFrameContext::agc.exposureMode
+ * \brief Exposure mode as set by the AeExposureMode control
+ *
+ * \var IPAFrameContext::agc.meteringMode
+ * \brief Metering mode as set by the AeMeteringMode control
+ *
+ * \var IPAFrameContext::agc.maxFrameDuration
+ * \brief Maximum frame duration as set by the FrameDurationLimits control
+ *
+ * \var IPAFrameContext::agc.updateMetering
+ * \brief Indicate if new ISP AGC metering parameters need to be applied
*/
/**
@@ -334,6 +390,18 @@ namespace libcamera::ipa::rkisp1 {
*/
/**
+ * \var IPAFrameContext::goc
+ * \brief Gamma out correction parameters for this frame
+ *
+ * \var IPAFrameContext::goc.gamma
+ * \brief Gamma value applied as 1.0/gamma
+ *
+ * \var IPAFrameContext::goc.update
+ * \brief Indicates if the goc parameters have been updated compared to the
+ * previous frame
+ */
+
+/**
* \var IPAFrameContext::sensor
* \brief Sensor configuration that used been used for this frame
*
diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
index bd02a7a2..8602b408 100644
--- a/src/ipa/rkisp1/ipa_context.h
+++ b/src/ipa/rkisp1/ipa_context.h
@@ -12,10 +12,12 @@
#include <libcamera/base/utils.h>
+#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/geometry.h>
#include <libipa/fc_queue.h>
+#include <libipa/matrix.h>
namespace libcamera {
@@ -68,8 +70,10 @@ struct IPAActiveState {
} automatic;
bool autoEnabled;
- uint32_t constraintMode;
- uint32_t exposureMode;
+ controls::AeConstraintModeEnum constraintMode;
+ controls::AeExposureModeEnum exposureMode;
+ controls::AeMeteringModeEnum meteringMode;
+ utils::Duration maxFrameDuration;
} agc;
struct {
@@ -104,6 +108,10 @@ struct IPAActiveState {
uint8_t denoise;
uint8_t sharpness;
} filter;
+
+ struct {
+ double gamma;
+ } goc;
};
struct IPAFrameContext : public FrameContext {
@@ -111,6 +119,11 @@ struct IPAFrameContext : public FrameContext {
uint32_t exposure;
double gain;
bool autoEnabled;
+ controls::AeConstraintModeEnum constraintMode;
+ controls::AeExposureModeEnum exposureMode;
+ controls::AeMeteringModeEnum meteringMode;
+ utils::Duration maxFrameDuration;
+ bool updateMetering;
} agc;
struct {
@@ -143,9 +156,18 @@ struct IPAFrameContext : public FrameContext {
} filter;
struct {
+ double gamma;
+ bool update;
+ } goc;
+
+ struct {
uint32_t exposure;
double gain;
} sensor;
+
+ struct {
+ Matrix<float, 3, 3> ccm;
+ } ccm;
};
struct IPAContext {
diff --git a/src/ipa/rkisp1/meson.build b/src/ipa/rkisp1/meson.build
index e813da53..e8b266f1 100644
--- a/src/ipa/rkisp1/meson.build
+++ b/src/ipa/rkisp1/meson.build
@@ -8,6 +8,7 @@ ipa_name = 'ipa_rkisp1'
rkisp1_ipa_sources = files([
'ipa_context.cpp',
'rkisp1.cpp',
+ 'utils.cpp',
])
rkisp1_ipa_sources += rkisp1_ipa_algorithms
@@ -15,9 +16,8 @@ rkisp1_ipa_sources += rkisp1_ipa_algorithms
mod = shared_module(ipa_name,
[rkisp1_ipa_sources, libcamera_generated_ipa_headers],
name_prefix : '',
- include_directories : [ipa_includes, libipa_includes],
- dependencies : libcamera_private,
- link_with : libipa,
+ include_directories : [ipa_includes],
+ dependencies : [libcamera_private, libipa_dep],
install : true,
install_dir : ipa_install_dir)
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index 6687c91e..d31cdbab 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -106,12 +106,8 @@ const IPAHwSettings ipaHwSettingsV12{
/* List of controls handled by the RkISP1 IPA */
const ControlInfoMap::Map rkisp1Controls{
- { &controls::AeEnable, ControlInfo(false, true) },
{ &controls::AwbEnable, ControlInfo(false, true) },
{ &controls::ColourGains, ControlInfo(0.0f, 3.996f, 1.0f) },
- { &controls::Brightness, ControlInfo(-1.0f, 0.993f, 0.0f) },
- { &controls::Contrast, ControlInfo(0.0f, 1.993f, 1.0f) },
- { &controls::Saturation, ControlInfo(0.0f, 1.993f, 1.0f) },
{ &controls::Sharpness, ControlInfo(0.0f, 10.0f, 1.0f) },
{ &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) },
};
@@ -427,7 +423,7 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo,
frameDurations[1],
frameDurations[2]);
- ctrlMap.merge(context_.ctrlMap);
+ ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end());
*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);
}
diff --git a/src/ipa/rkisp1/utils.cpp b/src/ipa/rkisp1/utils.cpp
new file mode 100644
index 00000000..960ec64e
--- /dev/null
+++ b/src/ipa/rkisp1/utils.cpp
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Miscellaneous utility functions specific to rkisp1
+ */
+
+#include "utils.h"
+
+/**
+ * \file utils.h
+ */
+
+namespace libcamera {
+
+namespace ipa::rkisp1::utils {
+
+/**
+ * \fn R floatingToFixedPoint(T number)
+ * \brief Convert a floating point number to a fixed-point representation
+ * \tparam I Bit width of the integer part of the fixed-point
+ * \tparam F Bit width of the fractional part of the fixed-point
+ * \tparam R Return type of the fixed-point representation
+ * \tparam T Input type of the floating point representation
+ * \param number The floating point number to convert to fixed point
+ * \return The converted value
+ */
+
+/**
+ * \fn R fixedToFloatingPoint(T number)
+ * \brief Convert a fixed-point number to a floating point representation
+ * \tparam I Bit width of the integer part of the fixed-point
+ * \tparam F Bit width of the fractional part of the fixed-point
+ * \tparam R Return type of the floating point representation
+ * \tparam T Input type of the fixed-point representation
+ * \param number The fixed point number to convert to floating point
+ * \return The converted value
+ */
+
+} /* namespace ipa::rkisp1::utils */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/utils.h b/src/ipa/rkisp1/utils.h
new file mode 100644
index 00000000..450f2244
--- /dev/null
+++ b/src/ipa/rkisp1/utils.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Miscellaneous utility functions specific to rkisp1
+ */
+
+#pragma once
+
+#include <cmath>
+#include <limits>
+#include <type_traits>
+
+namespace libcamera {
+
+namespace ipa::rkisp1::utils {
+
+#ifndef __DOXYGEN__
+template<unsigned int I, unsigned int F, typename R, typename T,
+ std::enable_if_t<std::is_integral_v<R> &&
+ std::is_floating_point_v<T>> * = nullptr>
+#else
+template<unsigned int I, unsigned int F, typename R, typename T>
+#endif
+constexpr R floatingToFixedPoint(T number)
+{
+ static_assert(sizeof(int) >= sizeof(R));
+ static_assert(I + F <= sizeof(R) * 8);
+
+ /*
+ * The intermediate cast to int is needed on arm platforms to properly
+ * cast negative values. See
+ * https://embeddeduse.com/2013/08/25/casting-a-negative-float-to-an-unsigned-int/
+ */
+ R mask = (1 << (F + I)) - 1;
+ R frac = static_cast<R>(static_cast<int>(std::round(number * (1 << F)))) & mask;
+
+ return frac;
+}
+
+#ifndef __DOXYGEN__
+template<unsigned int I, unsigned int F, typename R, typename T,
+ std::enable_if_t<std::is_floating_point_v<R> &&
+ std::is_integral_v<T>> * = nullptr>
+#else
+template<unsigned int I, unsigned int F, typename R, typename T>
+#endif
+constexpr R fixedToFloatingPoint(T number)
+{
+ static_assert(sizeof(int) >= sizeof(T));
+ static_assert(I + F <= sizeof(T) * 8);
+
+ /*
+ * Recreate the upper bits in case of a negative number by shifting the sign
+ * bit from the fixed point to the first bit of the unsigned and then right shifting
+ * by the same amount which keeps the sign bit in place.
+ * This can be optimized by the compiler quite well.
+ */
+ int remaining_bits = sizeof(int) * 8 - (I + F);
+ int t = static_cast<int>(static_cast<unsigned>(number) << remaining_bits) >> remaining_bits;
+ return static_cast<R>(t) / static_cast<R>(1 << F);
+}
+
+} /* namespace ipa::rkisp1::utils */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp
index 61490bd6..ee3848b5 100644
--- a/src/ipa/rpi/common/ipa_base.cpp
+++ b/src/ipa/rpi/common/ipa_base.cpp
@@ -73,7 +73,7 @@ const ControlInfoMap::Map ipaControls{
{ &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },
{ &controls::FrameDurationLimits, ControlInfo(INT64_C(33333), INT64_C(120000)) },
{ &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) },
- { &controls::rpi::StatsOutputEnable, ControlInfo(false, true) },
+ { &controls::rpi::StatsOutputEnable, ControlInfo(false, true, false) },
};
/* IPA controls handled conditionally, if the sensor is not mono */
@@ -103,8 +103,9 @@ LOG_DEFINE_CATEGORY(IPARPI)
namespace ipa::RPi {
IpaBase::IpaBase()
- : controller_(), frameLengths_(FrameLengthsQueueSize, 0s), stitchSwapBuffers_(false), frameCount_(0),
- mistrustCount_(0), lastRunTimestamp_(0), firstStart_(true), flickerState_({ 0, 0s })
+ : controller_(), frameLengths_(FrameLengthsQueueSize, 0s), statsMetadataOutput_(false),
+ stitchSwapBuffers_(false), frameCount_(0), mistrustCount_(0), lastRunTimestamp_(0),
+ firstStart_(true), flickerState_({ 0, 0s })
{
}
diff --git a/src/ipa/rpi/common/ipa_base.h b/src/ipa/rpi/common/ipa_base.h
index 074b304e..1a811beb 100644
--- a/src/ipa/rpi/common/ipa_base.h
+++ b/src/ipa/rpi/common/ipa_base.h
@@ -49,6 +49,11 @@ public:
void processStats(const ProcessParams &params) override;
protected:
+ bool monoSensor() const
+ {
+ return monoSensor_;
+ }
+
/* Raspberry Pi controller specific defines. */
std::unique_ptr<RPiController::CamHelper> helper_;
RPiController::Controller controller_;
diff --git a/src/ipa/rpi/controller/cac_status.h b/src/ipa/rpi/controller/cac_status.h
index 475d4c5c..adffce41 100644
--- a/src/ipa/rpi/controller/cac_status.h
+++ b/src/ipa/rpi/controller/cac_status.h
@@ -6,8 +6,6 @@
*/
#pragma once
-#include "pwl.h"
-
struct CacStatus {
std::vector<double> lutRx;
std::vector<double> lutRy;
diff --git a/src/ipa/rpi/controller/contrast_status.h b/src/ipa/rpi/controller/contrast_status.h
index 7c67f054..1f175872 100644
--- a/src/ipa/rpi/controller/contrast_status.h
+++ b/src/ipa/rpi/controller/contrast_status.h
@@ -6,7 +6,7 @@
*/
#pragma once
-#include "pwl.h"
+#include "libipa/pwl.h"
/*
* The "contrast" algorithm creates a gamma curve, optionally doing a little bit
@@ -14,7 +14,7 @@
*/
struct ContrastStatus {
- RPiController::Pwl gammaCurve;
+ libcamera::ipa::Pwl gammaCurve;
double brightness;
double contrast;
};
diff --git a/src/ipa/rpi/controller/meson.build b/src/ipa/rpi/controller/meson.build
index 32a4d31c..74b74888 100644
--- a/src/ipa/rpi/controller/meson.build
+++ b/src/ipa/rpi/controller/meson.build
@@ -5,7 +5,6 @@ rpi_ipa_controller_sources = files([
'controller.cpp',
'device_status.cpp',
'histogram.cpp',
- 'pwl.cpp',
'rpi/af.cpp',
'rpi/agc.cpp',
'rpi/agc_channel.cpp',
@@ -32,4 +31,5 @@ rpi_ipa_controller_deps = [
]
rpi_ipa_controller_lib = static_library('rpi_ipa_controller', rpi_ipa_controller_sources,
+ include_directories : libipa_includes,
dependencies : rpi_ipa_controller_deps)
diff --git a/src/ipa/rpi/controller/pwl.cpp b/src/ipa/rpi/controller/pwl.cpp
deleted file mode 100644
index e3912376..00000000
--- a/src/ipa/rpi/controller/pwl.cpp
+++ /dev/null
@@ -1,269 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * piecewise linear functions
- */
-
-#include <cassert>
-#include <cmath>
-#include <stdexcept>
-
-#include "pwl.h"
-
-using namespace RPiController;
-
-int Pwl::read(const libcamera::YamlObject &params)
-{
- if (!params.size() || params.size() % 2)
- return -EINVAL;
-
- const auto &list = params.asList();
-
- for (auto it = list.begin(); it != list.end(); it++) {
- auto x = it->get<double>();
- if (!x)
- return -EINVAL;
- if (it != list.begin() && *x <= points_.back().x)
- return -EINVAL;
-
- auto y = (++it)->get<double>();
- if (!y)
- return -EINVAL;
-
- points_.push_back(Point(*x, *y));
- }
-
- return 0;
-}
-
-void Pwl::append(double x, double y, const double eps)
-{
- if (points_.empty() || points_.back().x + eps < x)
- points_.push_back(Point(x, y));
-}
-
-void Pwl::prepend(double x, double y, const double eps)
-{
- if (points_.empty() || points_.front().x - eps > x)
- points_.insert(points_.begin(), Point(x, y));
-}
-
-Pwl::Interval Pwl::domain() const
-{
- return Interval(points_[0].x, points_[points_.size() - 1].x);
-}
-
-Pwl::Interval Pwl::range() const
-{
- double lo = points_[0].y, hi = lo;
- for (auto &p : points_)
- lo = std::min(lo, p.y), hi = std::max(hi, p.y);
- return Interval(lo, hi);
-}
-
-bool Pwl::empty() const
-{
- return points_.empty();
-}
-
-double Pwl::eval(double x, int *spanPtr, bool updateSpan) const
-{
- int span = findSpan(x, spanPtr && *spanPtr != -1 ? *spanPtr : points_.size() / 2 - 1);
- if (spanPtr && updateSpan)
- *spanPtr = span;
- return points_[span].y +
- (x - points_[span].x) * (points_[span + 1].y - points_[span].y) /
- (points_[span + 1].x - points_[span].x);
-}
-
-int Pwl::findSpan(double x, int span) const
-{
- /*
- * Pwls are generally small, so linear search may well be faster than
- * binary, though could review this if large PWls start turning up.
- */
- int lastSpan = points_.size() - 2;
- /*
- * some algorithms may call us with span pointing directly at the last
- * control point
- */
- span = std::max(0, std::min(lastSpan, span));
- while (span < lastSpan && x >= points_[span + 1].x)
- span++;
- while (span && x < points_[span].x)
- span--;
- return span;
-}
-
-Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span,
- const double eps) const
-{
- assert(span >= -1);
- bool prevOffEnd = false;
- for (span = span + 1; span < (int)points_.size() - 1; span++) {
- Point spanVec = points_[span + 1] - points_[span];
- double t = ((xy - points_[span]) % spanVec) / spanVec.len2();
- if (t < -eps) /* off the start of this span */
- {
- if (span == 0) {
- perp = points_[span];
- return PerpType::Start;
- } else if (prevOffEnd) {
- perp = points_[span];
- return PerpType::Vertex;
- }
- } else if (t > 1 + eps) /* off the end of this span */
- {
- if (span == (int)points_.size() - 2) {
- perp = points_[span + 1];
- return PerpType::End;
- }
- prevOffEnd = true;
- } else /* a true perpendicular */
- {
- perp = points_[span] + spanVec * t;
- return PerpType::Perpendicular;
- }
- }
- return PerpType::None;
-}
-
-Pwl Pwl::inverse(bool *trueInverse, const double eps) const
-{
- bool appended = false, prepended = false, neither = false;
- Pwl inverse;
-
- for (Point const &p : points_) {
- if (inverse.empty())
- inverse.append(p.y, p.x, eps);
- else if (std::abs(inverse.points_.back().x - p.y) <= eps ||
- std::abs(inverse.points_.front().x - p.y) <= eps)
- /* do nothing */;
- else if (p.y > inverse.points_.back().x) {
- inverse.append(p.y, p.x, eps);
- appended = true;
- } else if (p.y < inverse.points_.front().x) {
- inverse.prepend(p.y, p.x, eps);
- prepended = true;
- } else
- neither = true;
- }
-
- /*
- * This is not a proper inverse if we found ourselves putting points
- * onto both ends of the inverse, or if there were points that couldn't
- * go on either.
- */
- if (trueInverse)
- *trueInverse = !(neither || (appended && prepended));
-
- return inverse;
-}
-
-Pwl Pwl::compose(Pwl const &other, const double eps) const
-{
- double thisX = points_[0].x, thisY = points_[0].y;
- int thisSpan = 0, otherSpan = other.findSpan(thisY, 0);
- Pwl result({ { thisX, other.eval(thisY, &otherSpan, false) } });
- while (thisSpan != (int)points_.size() - 1) {
- double dx = points_[thisSpan + 1].x - points_[thisSpan].x,
- dy = points_[thisSpan + 1].y - points_[thisSpan].y;
- if (std::abs(dy) > eps &&
- otherSpan + 1 < (int)other.points_.size() &&
- points_[thisSpan + 1].y >=
- other.points_[otherSpan + 1].x + eps) {
- /*
- * next control point in result will be where this
- * function's y reaches the next span in other
- */
- thisX = points_[thisSpan].x +
- (other.points_[otherSpan + 1].x -
- points_[thisSpan].y) *
- dx / dy;
- thisY = other.points_[++otherSpan].x;
- } else if (std::abs(dy) > eps && otherSpan > 0 &&
- points_[thisSpan + 1].y <=
- other.points_[otherSpan - 1].x - eps) {
- /*
- * next control point in result will be where this
- * function's y reaches the previous span in other
- */
- thisX = points_[thisSpan].x +
- (other.points_[otherSpan + 1].x -
- points_[thisSpan].y) *
- dx / dy;
- thisY = other.points_[--otherSpan].x;
- } else {
- /* we stay in the same span in other */
- thisSpan++;
- thisX = points_[thisSpan].x,
- thisY = points_[thisSpan].y;
- }
- result.append(thisX, other.eval(thisY, &otherSpan, false),
- eps);
- }
- return result;
-}
-
-void Pwl::map(std::function<void(double x, double y)> f) const
-{
- for (auto &pt : points_)
- f(pt.x, pt.y);
-}
-
-void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1,
- std::function<void(double x, double y0, double y1)> f)
-{
- int span0 = 0, span1 = 0;
- double x = std::min(pwl0.points_[0].x, pwl1.points_[0].x);
- f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));
- while (span0 < (int)pwl0.points_.size() - 1 ||
- span1 < (int)pwl1.points_.size() - 1) {
- if (span0 == (int)pwl0.points_.size() - 1)
- x = pwl1.points_[++span1].x;
- else if (span1 == (int)pwl1.points_.size() - 1)
- x = pwl0.points_[++span0].x;
- else if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x)
- x = pwl1.points_[++span1].x;
- else
- x = pwl0.points_[++span0].x;
- f(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));
- }
-}
-
-Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1,
- std::function<double(double x, double y0, double y1)> f,
- const double eps)
-{
- Pwl result;
- map2(pwl0, pwl1, [&](double x, double y0, double y1) {
- result.append(x, f(x, y0, y1), eps);
- });
- return result;
-}
-
-void Pwl::matchDomain(Interval const &domain, bool clip, const double eps)
-{
- int span = 0;
- prepend(domain.start, eval(clip ? points_[0].x : domain.start, &span),
- eps);
- span = points_.size() - 2;
- append(domain.end, eval(clip ? points_.back().x : domain.end, &span),
- eps);
-}
-
-Pwl &Pwl::operator*=(double d)
-{
- for (auto &pt : points_)
- pt.y *= d;
- return *this;
-}
-
-void Pwl::debug(FILE *fp) const
-{
- fprintf(fp, "Pwl {\n");
- for (auto &p : points_)
- fprintf(fp, "\t(%g, %g)\n", p.x, p.y);
- fprintf(fp, "}\n");
-}
diff --git a/src/ipa/rpi/controller/pwl.h b/src/ipa/rpi/controller/pwl.h
deleted file mode 100644
index 7d5e7e4d..00000000
--- a/src/ipa/rpi/controller/pwl.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * piecewise linear functions interface
- */
-#pragma once
-
-#include <functional>
-#include <math.h>
-#include <vector>
-
-#include "libcamera/internal/yaml_parser.h"
-
-namespace RPiController {
-
-class Pwl
-{
-public:
- struct Interval {
- Interval(double _start, double _end)
- : start(_start), end(_end)
- {
- }
- double start, end;
- bool contains(double value)
- {
- return value >= start && value <= end;
- }
- double clip(double value)
- {
- return value < start ? start
- : (value > end ? end : value);
- }
- double len() const { return end - start; }
- };
- struct Point {
- Point() : x(0), y(0) {}
- Point(double _x, double _y)
- : x(_x), y(_y) {}
- double x, y;
- Point operator-(Point const &p) const
- {
- return Point(x - p.x, y - p.y);
- }
- Point operator+(Point const &p) const
- {
- return Point(x + p.x, y + p.y);
- }
- double operator%(Point const &p) const
- {
- return x * p.x + y * p.y;
- }
- Point operator*(double f) const { return Point(x * f, y * f); }
- Point operator/(double f) const { return Point(x / f, y / f); }
- double len2() const { return x * x + y * y; }
- double len() const { return sqrt(len2()); }
- };
- Pwl() {}
- Pwl(std::vector<Point> const &points) : points_(points) {}
- int read(const libcamera::YamlObject &params);
- void append(double x, double y, const double eps = 1e-6);
- void prepend(double x, double y, const double eps = 1e-6);
- Interval domain() const;
- Interval range() const;
- bool empty() const;
- /*
- * Evaluate Pwl, optionally supplying an initial guess for the
- * "span". The "span" may be optionally be updated. If you want to know
- * the "span" value but don't have an initial guess you can set it to
- * -1.
- */
- double eval(double x, int *spanPtr = nullptr,
- bool updateSpan = true) const;
- /*
- * Find perpendicular closest to xy, starting from span+1 so you can
- * call it repeatedly to check for multiple closest points (set span to
- * -1 on the first call). Also returns "pseudo" perpendiculars; see
- * PerpType enum.
- */
- enum class PerpType {
- None, /* no perpendicular found */
- Start, /* start of Pwl is closest point */
- End, /* end of Pwl is closest point */
- Vertex, /* vertex of Pwl is closest point */
- Perpendicular /* true perpendicular found */
- };
- PerpType invert(Point const &xy, Point &perp, int &span,
- const double eps = 1e-6) const;
- /*
- * Compute the inverse function. Indicate if it is a proper (true)
- * inverse, or only a best effort (e.g. input was non-monotonic).
- */
- Pwl inverse(bool *trueInverse = nullptr, const double eps = 1e-6) const;
- /* Compose two Pwls together, doing "this" first and "other" after. */
- Pwl compose(Pwl const &other, const double eps = 1e-6) const;
- /* Apply function to (x,y) values at every control point. */
- void map(std::function<void(double x, double y)> f) const;
- /*
- * Apply function to (x, y0, y1) values wherever either Pwl has a
- * control point.
- */
- static void map2(Pwl const &pwl0, Pwl const &pwl1,
- std::function<void(double x, double y0, double y1)> f);
- /*
- * Combine two Pwls, meaning we create a new Pwl where the y values are
- * given by running f wherever either has a knot.
- */
- static Pwl
- combine(Pwl const &pwl0, Pwl const &pwl1,
- std::function<double(double x, double y0, double y1)> f,
- const double eps = 1e-6);
- /*
- * Make "this" match (at least) the given domain. Any extension my be
- * clipped or linear.
- */
- void matchDomain(Interval const &domain, bool clip = true,
- const double eps = 1e-6);
- Pwl &operator*=(double d);
- void debug(FILE *fp = stdout) const;
-
-private:
- int findSpan(double x, int span) const;
- std::vector<Point> points_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp
index c5fd8482..5ca76dd9 100644
--- a/src/ipa/rpi/controller/rpi/af.cpp
+++ b/src/ipa/rpi/controller/rpi/af.cpp
@@ -139,7 +139,7 @@ int Af::CfgParams::read(const libcamera::YamlObject &params)
readNumber<uint32_t>(skipFrames, params, "skip_frames");
if (params.contains("map"))
- map.read(params["map"]);
+ map = params["map"].get<ipa::Pwl>(ipa::Pwl{});
else
LOG(RPiAf, Warning) << "No map defined";
@@ -721,7 +721,7 @@ bool Af::setLensPosition(double dioptres, int *hwpos)
if (mode_ == AfModeManual) {
LOG(RPiAf, Debug) << "setLensPosition: " << dioptres;
- ftarget_ = cfg_.map.domain().clip(dioptres);
+ ftarget_ = cfg_.map.domain().clamp(dioptres);
changed = !(initted_ && fsmooth_ == ftarget_);
updateLensPosition();
}
diff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h
index 2617e2ac..317a51f3 100644
--- a/src/ipa/rpi/controller/rpi/af.h
+++ b/src/ipa/rpi/controller/rpi/af.h
@@ -9,7 +9,8 @@
#include "../af_algorithm.h"
#include "../af_status.h"
#include "../pdaf_data.h"
-#include "../pwl.h"
+
+#include "libipa/pwl.h"
/*
* This algorithm implements a hybrid of CDAF and PDAF, favouring PDAF.
@@ -100,7 +101,7 @@ private:
uint32_t confThresh; /* PDAF confidence cell min (sensor-specific) */
uint32_t confClip; /* PDAF confidence cell max (sensor-specific) */
uint32_t skipFrames; /* frames to skip at start or modeswitch */
- Pwl map; /* converts dioptres -> lens driver position */
+ libcamera::ipa::Pwl map; /* converts dioptres -> lens driver position */
CfgParams();
int read(const libcamera::YamlObject &params);
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
index a77ccec3..cf2565a8 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
+++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
@@ -130,7 +130,8 @@ int AgcConstraint::read(const libcamera::YamlObject &params)
return -EINVAL;
qHi = *value;
- return yTarget.read(params["y_target"]);
+ yTarget = params["y_target"].get<ipa::Pwl>(ipa::Pwl{});
+ return yTarget.empty() ? -EINVAL : 0;
}
static std::tuple<int, AgcConstraintMode>
@@ -237,9 +238,9 @@ int AgcConfig::read(const libcamera::YamlObject &params)
return ret;
}
- ret = yTarget.read(params["y_target"]);
- if (ret)
- return ret;
+ yTarget = params["y_target"].get<ipa::Pwl>(ipa::Pwl{});
+ if (yTarget.empty())
+ return -EINVAL;
speed = params["speed"].get<double>(0.2);
startupFrames = params["startup_frames"].get<uint16_t>(10);
@@ -715,7 +716,7 @@ static constexpr double EvGainYTargetLimit = 0.9;
static double constraintComputeGain(AgcConstraint &c, const Histogram &h, double lux,
double evGain, double &targetY)
{
- targetY = c.yTarget.eval(c.yTarget.domain().clip(lux));
+ targetY = c.yTarget.eval(c.yTarget.domain().clamp(lux));
targetY = std::min(EvGainYTargetLimit, targetY * evGain);
double iqm = h.interQuantileMean(c.qLo, c.qHi);
return (targetY * h.bins()) / iqm;
@@ -734,7 +735,7 @@ void AgcChannel::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,
* The initial gain and target_Y come from some of the regions. After
* that we consider the histogram constraints.
*/
- targetY = config_.yTarget.eval(config_.yTarget.domain().clip(lux.lux));
+ targetY = config_.yTarget.eval(config_.yTarget.domain().clamp(lux.lux));
targetY = std::min(EvGainYTargetLimit, targetY * evGain);
/*
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h
index 99033e23..58368889 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.h
+++ b/src/ipa/rpi/controller/rpi/agc_channel.h
@@ -12,10 +12,11 @@
#include <libcamera/base/utils.h>
+#include <libipa/pwl.h>
+
#include "../agc_status.h"
#include "../awb_status.h"
#include "../controller.h"
-#include "../pwl.h"
/* This is our implementation of AGC. */
@@ -40,7 +41,7 @@ struct AgcConstraint {
Bound bound;
double qLo;
double qHi;
- Pwl yTarget;
+ libcamera::ipa::Pwl yTarget;
int read(const libcamera::YamlObject &params);
};
@@ -61,7 +62,7 @@ struct AgcConfig {
std::map<std::string, AgcExposureMode> exposureModes;
std::map<std::string, AgcConstraintMode> constraintModes;
std::vector<AgcChannelConstraint> channelConstraints;
- Pwl yTarget;
+ libcamera::ipa::Pwl yTarget;
double speed;
uint16_t startupFrames;
unsigned int convergenceFrames;
diff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp
index abe5906e..003c8fa1 100644
--- a/src/ipa/rpi/controller/rpi/awb.cpp
+++ b/src/ipa/rpi/controller/rpi/awb.cpp
@@ -49,10 +49,11 @@ int AwbPrior::read(const libcamera::YamlObject &params)
return -EINVAL;
lux = *value;
- return prior.read(params["prior"]);
+ prior = params["prior"].get<ipa::Pwl>(ipa::Pwl{});
+ return prior.empty() ? -EINVAL : 0;
}
-static int readCtCurve(Pwl &ctR, Pwl &ctB, const libcamera::YamlObject &params)
+static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::YamlObject &params)
{
if (params.size() % 3) {
LOG(RPiAwb, Error) << "AwbConfig: incomplete CT curve entry";
@@ -103,8 +104,8 @@ int AwbConfig::read(const libcamera::YamlObject &params)
if (ret)
return ret;
/* We will want the inverse functions of these too. */
- ctRInverse = ctR.inverse();
- ctBInverse = ctB.inverse();
+ ctRInverse = ctR.inverse().first;
+ ctBInverse = ctB.inverse().first;
}
if (params.contains("priors")) {
@@ -207,7 +208,7 @@ void Awb::initialise()
* them.
*/
if (!config_.ctR.empty() && !config_.ctB.empty()) {
- syncResults_.temperatureK = config_.ctR.domain().clip(4000);
+ syncResults_.temperatureK = config_.ctR.domain().clamp(4000);
syncResults_.gainR = 1.0 / config_.ctR.eval(syncResults_.temperatureK);
syncResults_.gainG = 1.0;
syncResults_.gainB = 1.0 / config_.ctB.eval(syncResults_.temperatureK);
@@ -273,8 +274,8 @@ void Awb::setManualGains(double manualR, double manualB)
syncResults_.gainB = prevSyncResults_.gainB = manualB_;
if (config_.bayes) {
/* Also estimate the best corresponding colour temperature from the curves. */
- double ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clip(1 / manualR_));
- double ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clip(1 / manualB_));
+ double ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clamp(1 / manualR_));
+ double ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clamp(1 / manualB_));
prevSyncResults_.temperatureK = (ctR + ctB) / 2;
syncResults_.temperatureK = prevSyncResults_.temperatureK;
}
@@ -468,7 +469,7 @@ double Awb::computeDelta2Sum(double gainR, double gainB)
return delta2Sum;
}
-Pwl Awb::interpolatePrior()
+ipa::Pwl Awb::interpolatePrior()
{
/*
* Interpolate the prior log likelihood function for our current lux
@@ -485,7 +486,7 @@ Pwl Awb::interpolatePrior()
idx++;
double lux0 = config_.priors[idx].lux,
lux1 = config_.priors[idx + 1].lux;
- return Pwl::combine(config_.priors[idx].prior,
+ return ipa::Pwl::combine(config_.priors[idx].prior,
config_.priors[idx + 1].prior,
[&](double /*x*/, double y0, double y1) {
return y0 + (y1 - y0) *
@@ -494,26 +495,26 @@ Pwl Awb::interpolatePrior()
}
}
-static double interpolateQuadatric(Pwl::Point const &a, Pwl::Point const &b,
- Pwl::Point const &c)
+static double interpolateQuadatric(ipa::Pwl::Point const &a, ipa::Pwl::Point const &b,
+ ipa::Pwl::Point const &c)
{
/*
* Given 3 points on a curve, find the extremum of the function in that
* interval by fitting a quadratic.
*/
const double eps = 1e-3;
- Pwl::Point ca = c - a, ba = b - a;
- double denominator = 2 * (ba.y * ca.x - ca.y * ba.x);
+ ipa::Pwl::Point ca = c - a, ba = b - a;
+ double denominator = 2 * (ba.y() * ca.x() - ca.y() * ba.x());
if (abs(denominator) > eps) {
- double numerator = ba.y * ca.x * ca.x - ca.y * ba.x * ba.x;
- double result = numerator / denominator + a.x;
- return std::max(a.x, std::min(c.x, result));
+ double numerator = ba.y() * ca.x() * ca.x() - ca.y() * ba.x() * ba.x();
+ double result = numerator / denominator + a.x();
+ return std::max(a.x(), std::min(c.x(), result));
}
/* has degenerated to straight line segment */
- return a.y < c.y - eps ? a.x : (c.y < a.y - eps ? c.x : b.x);
+ return a.y() < c.y() - eps ? a.x() : (c.y() < a.y() - eps ? c.x() : b.x());
}
-double Awb::coarseSearch(Pwl const &prior)
+double Awb::coarseSearch(ipa::Pwl const &prior)
{
points_.clear(); /* assume doesn't deallocate memory */
size_t bestPoint = 0;
@@ -525,22 +526,22 @@ double Awb::coarseSearch(Pwl const &prior)
double b = config_.ctB.eval(t, &spanB);
double gainR = 1 / r, gainB = 1 / b;
double delta2Sum = computeDelta2Sum(gainR, gainB);
- double priorLogLikelihood = prior.eval(prior.domain().clip(t));
+ double priorLogLikelihood = prior.eval(prior.domain().clamp(t));
double finalLogLikelihood = delta2Sum - priorLogLikelihood;
LOG(RPiAwb, Debug)
<< "t: " << t << " gain R " << gainR << " gain B "
<< gainB << " delta2_sum " << delta2Sum
<< " prior " << priorLogLikelihood << " final "
<< finalLogLikelihood;
- points_.push_back(Pwl::Point(t, finalLogLikelihood));
- if (points_.back().y < points_[bestPoint].y)
+ points_.push_back(ipa::Pwl::Point({ t, finalLogLikelihood }));
+ if (points_.back().y() < points_[bestPoint].y())
bestPoint = points_.size() - 1;
if (t == mode_->ctHi)
break;
/* for even steps along the r/b curve scale them by the current t */
t = std::min(t + t / 10 * config_.coarseStep, mode_->ctHi);
}
- t = points_[bestPoint].x;
+ t = points_[bestPoint].x();
LOG(RPiAwb, Debug) << "Coarse search found CT " << t;
/*
* We have the best point of the search, but refine it with a quadratic
@@ -559,7 +560,7 @@ double Awb::coarseSearch(Pwl const &prior)
return t;
}
-void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)
+void Awb::fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior)
{
int spanR = -1, spanB = -1;
config_.ctR.eval(t, &spanR);
@@ -570,14 +571,14 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)
config_.ctR.eval(t - nsteps * step, &spanR);
double bDiff = config_.ctB.eval(t + nsteps * step, &spanB) -
config_.ctB.eval(t - nsteps * step, &spanB);
- Pwl::Point transverse(bDiff, -rDiff);
- if (transverse.len2() < 1e-6)
+ ipa::Pwl::Point transverse({ bDiff, -rDiff });
+ if (transverse.length2() < 1e-6)
return;
/*
* unit vector orthogonal to the b vs. r function (pointing outwards
* with r and b increasing)
*/
- transverse = transverse / transverse.len();
+ transverse = transverse / transverse.length();
double bestLogLikelihood = 0, bestT = 0, bestR = 0, bestB = 0;
double transverseRange = config_.transverseNeg + config_.transversePos;
const int maxNumDeltas = 12;
@@ -592,26 +593,26 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)
for (int i = -nsteps; i <= nsteps; i++) {
double tTest = t + i * step;
double priorLogLikelihood =
- prior.eval(prior.domain().clip(tTest));
+ prior.eval(prior.domain().clamp(tTest));
double rCurve = config_.ctR.eval(tTest, &spanR);
double bCurve = config_.ctB.eval(tTest, &spanB);
/* x will be distance off the curve, y the log likelihood there */
- Pwl::Point points[maxNumDeltas];
+ ipa::Pwl::Point points[maxNumDeltas];
int bestPoint = 0;
/* Take some measurements transversely *off* the CT curve. */
for (int j = 0; j < numDeltas; j++) {
- points[j].x = -config_.transverseNeg +
- (transverseRange * j) / (numDeltas - 1);
- Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) +
- transverse * points[j].x;
- double rTest = rbTest.x, bTest = rbTest.y;
+ points[j][0] = -config_.transverseNeg +
+ (transverseRange * j) / (numDeltas - 1);
+ ipa::Pwl::Point rbTest = ipa::Pwl::Point({ rCurve, bCurve }) +
+ transverse * points[j].x();
+ double rTest = rbTest.x(), bTest = rbTest.y();
double gainR = 1 / rTest, gainB = 1 / bTest;
double delta2Sum = computeDelta2Sum(gainR, gainB);
- points[j].y = delta2Sum - priorLogLikelihood;
+ points[j][1] = delta2Sum - priorLogLikelihood;
LOG(RPiAwb, Debug)
<< "At t " << tTest << " r " << rTest << " b "
- << bTest << ": " << points[j].y;
- if (points[j].y < points[bestPoint].y)
+ << bTest << ": " << points[j].y();
+ if (points[j].y() < points[bestPoint].y())
bestPoint = j;
}
/*
@@ -619,11 +620,11 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)
* now let's do a quadratic interpolation for the best result.
*/
bestPoint = std::max(1, std::min(bestPoint, numDeltas - 2));
- Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) +
- transverse * interpolateQuadatric(points[bestPoint - 1],
- points[bestPoint],
- points[bestPoint + 1]);
- double rTest = rbTest.x, bTest = rbTest.y;
+ ipa::Pwl::Point rbTest = ipa::Pwl::Point({ rCurve, bCurve }) +
+ transverse * interpolateQuadatric(points[bestPoint - 1],
+ points[bestPoint],
+ points[bestPoint + 1]);
+ double rTest = rbTest.x(), bTest = rbTest.y();
double gainR = 1 / rTest, gainB = 1 / bTest;
double delta2Sum = computeDelta2Sum(gainR, gainB);
double finalLogLikelihood = delta2Sum - priorLogLikelihood;
@@ -653,7 +654,7 @@ void Awb::awbBayes()
* Get the current prior, and scale according to how many zones are
* valid... not entirely sure about this.
*/
- Pwl prior = interpolatePrior();
+ ipa::Pwl prior = interpolatePrior();
prior *= zones_.size() / (double)(statistics_->awbRegions.numRegions());
prior.map([](double x, double y) {
LOG(RPiAwb, Debug) << "(" << x << "," << y << ")";
diff --git a/src/ipa/rpi/controller/rpi/awb.h b/src/ipa/rpi/controller/rpi/awb.h
index 499b4519..ab30f4fa 100644
--- a/src/ipa/rpi/controller/rpi/awb.h
+++ b/src/ipa/rpi/controller/rpi/awb.h
@@ -10,11 +10,14 @@
#include <condition_variable>
#include <thread>
+#include <libcamera/geometry.h>
+
#include "../awb_algorithm.h"
-#include "../pwl.h"
#include "../awb_status.h"
#include "../statistics.h"
+#include "libipa/pwl.h"
+
namespace RPiController {
/* Control algorithm to perform AWB calculations. */
@@ -28,7 +31,7 @@ struct AwbMode {
struct AwbPrior {
int read(const libcamera::YamlObject &params);
double lux; /* lux level */
- Pwl prior; /* maps CT to prior log likelihood for this lux level */
+ libcamera::ipa::Pwl prior; /* maps CT to prior log likelihood for this lux level */
};
struct AwbConfig {
@@ -41,10 +44,10 @@ struct AwbConfig {
unsigned int convergenceFrames; /* approx number of frames to converge */
double speed; /* IIR filter speed applied to algorithm results */
bool fast; /* "fast" mode uses a 16x16 rather than 32x32 grid */
- Pwl ctR; /* function maps CT to r (= R/G) */
- Pwl ctB; /* function maps CT to b (= B/G) */
- Pwl ctRInverse; /* inverse of ctR */
- Pwl ctBInverse; /* inverse of ctB */
+ libcamera::ipa::Pwl ctR; /* function maps CT to r (= R/G) */
+ libcamera::ipa::Pwl ctB; /* function maps CT to b (= B/G) */
+ libcamera::ipa::Pwl ctRInverse; /* inverse of ctR */
+ libcamera::ipa::Pwl ctBInverse; /* inverse of ctB */
/* table of illuminant priors at different lux levels */
std::vector<AwbPrior> priors;
/* AWB "modes" (determines the search range) */
@@ -161,11 +164,11 @@ private:
void awbGrey();
void prepareStats();
double computeDelta2Sum(double gainR, double gainB);
- Pwl interpolatePrior();
- double coarseSearch(Pwl const &prior);
- void fineSearch(double &t, double &r, double &b, Pwl const &prior);
+ libcamera::ipa::Pwl interpolatePrior();
+ double coarseSearch(libcamera::ipa::Pwl const &prior);
+ void fineSearch(double &t, double &r, double &b, libcamera::ipa::Pwl const &prior);
std::vector<RGB> zones_;
- std::vector<Pwl::Point> points_;
+ std::vector<libcamera::ipa::Pwl::Point> points_;
/* manual r setting */
double manualR_;
/* manual b setting */
diff --git a/src/ipa/rpi/controller/rpi/ccm.cpp b/src/ipa/rpi/controller/rpi/ccm.cpp
index c5588029..e673964c 100644
--- a/src/ipa/rpi/controller/rpi/ccm.cpp
+++ b/src/ipa/rpi/controller/rpi/ccm.cpp
@@ -71,9 +71,9 @@ int Ccm::read(const libcamera::YamlObject &params)
int ret;
if (params.contains("saturation")) {
- ret = config_.saturation.read(params["saturation"]);
- if (ret)
- return ret;
+ config_.saturation = params["saturation"].get<ipa::Pwl>(ipa::Pwl{});
+ if (config_.saturation.empty())
+ return -EINVAL;
}
for (auto &p : params["ccms"].asList()) {
@@ -172,7 +172,7 @@ void Ccm::prepare(Metadata *imageMetadata)
ccmStatus.saturation = saturation;
if (!config_.saturation.empty())
saturation *= config_.saturation.eval(
- config_.saturation.domain().clip(lux.lux));
+ config_.saturation.domain().clamp(lux.lux));
ccm = applySaturation(ccm, saturation);
for (int j = 0; j < 3; j++)
for (int i = 0; i < 3; i++)
diff --git a/src/ipa/rpi/controller/rpi/ccm.h b/src/ipa/rpi/controller/rpi/ccm.h
index b3abeddf..4e5b33fe 100644
--- a/src/ipa/rpi/controller/rpi/ccm.h
+++ b/src/ipa/rpi/controller/rpi/ccm.h
@@ -8,8 +8,9 @@
#include <vector>
+#include <libipa/pwl.h>
+
#include "../ccm_algorithm.h"
-#include "../pwl.h"
namespace RPiController {
@@ -54,7 +55,7 @@ struct CtCcm {
struct CcmConfig {
std::vector<CtCcm> ccms;
- Pwl saturation;
+ libcamera::ipa::Pwl saturation;
};
class Ccm : public CcmAlgorithm
diff --git a/src/ipa/rpi/controller/rpi/contrast.cpp b/src/ipa/rpi/controller/rpi/contrast.cpp
index 9eef792d..9b37943a 100644
--- a/src/ipa/rpi/controller/rpi/contrast.cpp
+++ b/src/ipa/rpi/controller/rpi/contrast.cpp
@@ -53,7 +53,9 @@ int Contrast::read(const libcamera::YamlObject &params)
config_.hiHistogram = params["hi_histogram"].get<double>(0.95);
config_.hiLevel = params["hi_level"].get<double>(0.95);
config_.hiMax = params["hi_max"].get<double>(2000);
- return config_.gammaCurve.read(params["gamma_curve"]);
+
+ config_.gammaCurve = params["gamma_curve"].get<ipa::Pwl>(ipa::Pwl{});
+ return config_.gammaCurve.empty() ? -EINVAL : 0;
}
void Contrast::setBrightness(double brightness)
@@ -92,10 +94,10 @@ void Contrast::prepare(Metadata *imageMetadata)
imageMetadata->set("contrast.status", status_);
}
-Pwl computeStretchCurve(Histogram const &histogram,
+ipa::Pwl computeStretchCurve(Histogram const &histogram,
ContrastConfig const &config)
{
- Pwl enhance;
+ ipa::Pwl enhance;
enhance.append(0, 0);
/*
* If the start of the histogram is rather empty, try to pull it down a
@@ -136,10 +138,10 @@ Pwl computeStretchCurve(Histogram const &histogram,
return enhance;
}
-Pwl applyManualContrast(Pwl const &gammaCurve, double brightness,
- double contrast)
+ipa::Pwl applyManualContrast(ipa::Pwl const &gammaCurve, double brightness,
+ double contrast)
{
- Pwl newGammaCurve;
+ ipa::Pwl newGammaCurve;
LOG(RPiContrast, Debug)
<< "Manual brightness " << brightness << " contrast " << contrast;
gammaCurve.map([&](double x, double y) {
@@ -160,7 +162,7 @@ void Contrast::process(StatisticsPtr &stats,
* ways: 1. Adjust the gamma curve so as to pull the start of the
* histogram down, and possibly push the end up.
*/
- Pwl gammaCurve = config_.gammaCurve;
+ ipa::Pwl gammaCurve = config_.gammaCurve;
if (ceEnable_) {
if (config_.loMax != 0 || config_.hiMax != 0)
gammaCurve = computeStretchCurve(histogram, config_).compose(gammaCurve);
diff --git a/src/ipa/rpi/controller/rpi/contrast.h b/src/ipa/rpi/controller/rpi/contrast.h
index a9d9bbc9..c0f7db98 100644
--- a/src/ipa/rpi/controller/rpi/contrast.h
+++ b/src/ipa/rpi/controller/rpi/contrast.h
@@ -8,8 +8,9 @@
#include <mutex>
+#include <libipa/pwl.h>
+
#include "../contrast_algorithm.h"
-#include "../pwl.h"
namespace RPiController {
@@ -26,7 +27,7 @@ struct ContrastConfig {
double hiHistogram;
double hiLevel;
double hiMax;
- Pwl gammaCurve;
+ libcamera::ipa::Pwl gammaCurve;
};
class Contrast : public ContrastAlgorithm
diff --git a/src/ipa/rpi/controller/rpi/geq.cpp b/src/ipa/rpi/controller/rpi/geq.cpp
index fb539d1f..40e7191b 100644
--- a/src/ipa/rpi/controller/rpi/geq.cpp
+++ b/src/ipa/rpi/controller/rpi/geq.cpp
@@ -9,7 +9,6 @@
#include "../device_status.h"
#include "../lux_status.h"
-#include "../pwl.h"
#include "geq.h"
@@ -45,9 +44,9 @@ int Geq::read(const libcamera::YamlObject &params)
}
if (params.contains("strength")) {
- int ret = config_.strength.read(params["strength"]);
- if (ret)
- return ret;
+ config_.strength = params["strength"].get<ipa::Pwl>(ipa::Pwl{});
+ if (config_.strength.empty())
+ return -EINVAL;
}
return 0;
@@ -67,7 +66,7 @@ void Geq::prepare(Metadata *imageMetadata)
GeqStatus geqStatus = {};
double strength = config_.strength.empty()
? 1.0
- : config_.strength.eval(config_.strength.domain().clip(luxStatus.lux));
+ : config_.strength.eval(config_.strength.domain().clamp(luxStatus.lux));
strength *= deviceStatus.analogueGain;
double offset = config_.offset * strength;
double slope = config_.slope * strength;
diff --git a/src/ipa/rpi/controller/rpi/geq.h b/src/ipa/rpi/controller/rpi/geq.h
index 2c8400c2..e8b9f427 100644
--- a/src/ipa/rpi/controller/rpi/geq.h
+++ b/src/ipa/rpi/controller/rpi/geq.h
@@ -6,6 +6,8 @@
*/
#pragma once
+#include <libipa/pwl.h>
+
#include "../algorithm.h"
#include "../geq_status.h"
@@ -16,7 +18,7 @@ namespace RPiController {
struct GeqConfig {
uint16_t offset;
double slope;
- Pwl strength; /* lux to strength factor */
+ libcamera::ipa::Pwl strength; /* lux to strength factor */
};
class Geq : public Algorithm
diff --git a/src/ipa/rpi/controller/rpi/hdr.cpp b/src/ipa/rpi/controller/rpi/hdr.cpp
index 34cf360e..f3da8291 100644
--- a/src/ipa/rpi/controller/rpi/hdr.cpp
+++ b/src/ipa/rpi/controller/rpi/hdr.cpp
@@ -42,7 +42,7 @@ void HdrConfig::read(const libcamera::YamlObject &params, const std::string &mod
/* Lens shading related parameters. */
if (params.contains("spatial_gain_curve")) {
- spatialGainCurve.read(params["spatial_gain_curve"]);
+ spatialGainCurve = params["spatial_gain_curve"].get<ipa::Pwl>(ipa::Pwl{});
} else if (params.contains("spatial_gain")) {
double spatialGain = params["spatial_gain"].get<double>(2.0);
spatialGainCurve.append(0.0, spatialGain);
@@ -66,7 +66,7 @@ void HdrConfig::read(const libcamera::YamlObject &params, const std::string &mod
iirStrength = params["iir_strength"].get<double>(8.0);
strength = params["strength"].get<double>(1.5);
if (tonemapEnable)
- tonemap.read(params["tonemap"]);
+ tonemap = params["tonemap"].get<ipa::Pwl>(ipa::Pwl{});
speed = params["speed"].get<double>(1.0);
if (params.contains("hi_quantile_targets")) {
hiQuantileTargets = params["hi_quantile_targets"].getList<double>().value();
@@ -212,7 +212,7 @@ bool Hdr::updateTonemap([[maybe_unused]] StatisticsPtr &stats, HdrConfig &config
/* When there's a change of HDR mode we start over with a new tonemap curve. */
if (delayedStatus_.mode != previousMode_) {
previousMode_ = delayedStatus_.mode;
- tonemap_ = Pwl();
+ tonemap_ = ipa::Pwl();
}
/* No tonemapping. No need to output a tonemap.status. */
@@ -275,7 +275,7 @@ bool Hdr::updateTonemap([[maybe_unused]] StatisticsPtr &stats, HdrConfig &config
double power = std::clamp(min_power, config.powerMin, config.powerMax);
/* Generate the tonemap, including the contrast adjustment factors. */
- Pwl tonemap;
+ libcamera::ipa::Pwl tonemap;
tonemap.append(0, 0);
for (unsigned int i = 0; i <= 6; i++) {
double x = 1 << (i + 9); /* x loops from 512 to 32768 inclusive */
diff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h
index 9b7327f8..5c2f3988 100644
--- a/src/ipa/rpi/controller/rpi/hdr.h
+++ b/src/ipa/rpi/controller/rpi/hdr.h
@@ -12,9 +12,10 @@
#include <libcamera/geometry.h>
+#include <libipa/pwl.h>
+
#include "../hdr_algorithm.h"
#include "../hdr_status.h"
-#include "../pwl.h"
/* This is our implementation of an HDR algorithm. */
@@ -26,7 +27,7 @@ struct HdrConfig {
std::map<unsigned int, std::string> channelMap;
/* Lens shading related parameters. */
- Pwl spatialGainCurve; /* Brightness to gain curve for different image regions. */
+ libcamera::ipa::Pwl spatialGainCurve; /* Brightness to gain curve for different image regions. */
unsigned int diffusion; /* How much to diffuse the gain spatially. */
/* Tonemap related parameters. */
@@ -35,7 +36,7 @@ struct HdrConfig {
double detailSlope;
double iirStrength;
double strength;
- Pwl tonemap;
+ libcamera::ipa::Pwl tonemap;
/* These relate to adaptive tonemap calculation. */
double speed;
std::vector<double> hiQuantileTargets; /* quantiles to check for unsaturated images */
@@ -75,7 +76,7 @@ private:
HdrStatus status_; /* track the current HDR mode and channel */
HdrStatus delayedStatus_; /* track the delayed HDR mode and channel */
std::string previousMode_;
- Pwl tonemap_;
+ libcamera::ipa::Pwl tonemap_;
libcamera::Size regions_; /* stats regions */
unsigned int numRegions_; /* total number of stats regions */
std::vector<double> gains_[2];
diff --git a/src/ipa/rpi/controller/rpi/tonemap.cpp b/src/ipa/rpi/controller/rpi/tonemap.cpp
index 0426e972..3422adfe 100644
--- a/src/ipa/rpi/controller/rpi/tonemap.cpp
+++ b/src/ipa/rpi/controller/rpi/tonemap.cpp
@@ -33,7 +33,7 @@ int Tonemap::read(const libcamera::YamlObject &params)
config_.detailSlope = params["detail_slope"].get<double>(0.1);
config_.iirStrength = params["iir_strength"].get<double>(1.0);
config_.strength = params["strength"].get<double>(1.0);
- config_.tonemap.read(params["tone_curve"]);
+ config_.tonemap = params["tone_curve"].get<ipa::Pwl>(ipa::Pwl{});
return 0;
}
diff --git a/src/ipa/rpi/controller/rpi/tonemap.h b/src/ipa/rpi/controller/rpi/tonemap.h
index f25aa47f..ba0cf5c4 100644
--- a/src/ipa/rpi/controller/rpi/tonemap.h
+++ b/src/ipa/rpi/controller/rpi/tonemap.h
@@ -6,8 +6,9 @@
*/
#pragma once
+#include <libipa/pwl.h>
+
#include "algorithm.h"
-#include "pwl.h"
namespace RPiController {
@@ -16,7 +17,7 @@ struct TonemapConfig {
double detailSlope;
double iirStrength;
double strength;
- Pwl tonemap;
+ libcamera::ipa::Pwl tonemap;
};
class Tonemap : public Algorithm
diff --git a/src/ipa/rpi/controller/tonemap_status.h b/src/ipa/rpi/controller/tonemap_status.h
index 41a7bf2f..0364ff66 100644
--- a/src/ipa/rpi/controller/tonemap_status.h
+++ b/src/ipa/rpi/controller/tonemap_status.h
@@ -6,12 +6,12 @@
*/
#pragma once
-#include "pwl.h"
+#include <libipa/pwl.h>
struct TonemapStatus {
uint16_t detailConstant;
double detailSlope;
double iirStrength;
double strength;
- RPiController::Pwl tonemap;
+ libcamera::ipa::Pwl tonemap;
};
diff --git a/src/ipa/rpi/vc4/meson.build b/src/ipa/rpi/vc4/meson.build
index 590e9197..63fc5925 100644
--- a/src/ipa/rpi/vc4/meson.build
+++ b/src/ipa/rpi/vc4/meson.build
@@ -15,7 +15,6 @@ vc4_ipa_libs = [
vc4_ipa_includes = [
ipa_includes,
- libipa_includes,
]
vc4_ipa_sources = files([
@@ -28,8 +27,7 @@ mod = shared_module(ipa_name,
[vc4_ipa_sources, libcamera_generated_ipa_headers],
name_prefix : '',
include_directories : vc4_ipa_includes,
- dependencies : vc4_ipa_deps,
- link_with : libipa,
+ dependencies : [vc4_ipa_deps, libipa_dep],
link_whole : vc4_ipa_libs,
install : true,
install_dir : ipa_install_dir)
diff --git a/src/ipa/simple/black_level.cpp b/src/ipa/simple/black_level.cpp
index 53b4c040..cc490eb5 100644
--- a/src/ipa/simple/black_level.cpp
+++ b/src/ipa/simple/black_level.cpp
@@ -43,7 +43,7 @@ BlackLevel::BlackLevel()
* \return The black level, in the range from 0 (minimum) to 255 (maximum).
* If the black level couldn't be determined yet, return 0.
*/
-unsigned int BlackLevel::get() const
+uint8_t BlackLevel::get() const
{
return blackLevelSet_ ? blackLevel_ : 0;
}
diff --git a/src/ipa/simple/black_level.h b/src/ipa/simple/black_level.h
index 25094217..5e032f9f 100644
--- a/src/ipa/simple/black_level.h
+++ b/src/ipa/simple/black_level.h
@@ -8,6 +8,7 @@
#pragma once
#include <array>
+#include <stdint.h>
#include "libcamera/internal/software_isp/swisp_stats.h"
@@ -17,11 +18,11 @@ class BlackLevel
{
public:
BlackLevel();
- unsigned int get() const;
+ uint8_t get() const;
void update(SwIspStats::Histogram &yHistogram);
private:
- unsigned int blackLevel_;
+ uint8_t blackLevel_;
bool blackLevelSet_;
};
diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build
index 44b5f1d7..33d1c96a 100644
--- a/src/ipa/simple/meson.build
+++ b/src/ipa/simple/meson.build
@@ -10,9 +10,8 @@ soft_simple_sources = files([
mod = shared_module(ipa_name,
[soft_simple_sources, libcamera_generated_ipa_headers],
name_prefix : '',
- include_directories : [ipa_includes, libipa_includes],
- dependencies : libcamera_private,
- link_with : libipa,
+ include_directories : [ipa_includes],
+ dependencies : [libcamera_private, libipa_dep],
install : true,
install_dir : ipa_install_dir)
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index a5bb2bbf..b7746ce0 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -5,6 +5,9 @@
* Simple Software Image Processing Algorithm module
*/
+#include <cmath>
+#include <numeric>
+#include <stdint.h>
#include <sys/mman.h>
#include <linux/v4l2-controls.h>
@@ -82,6 +85,10 @@ private:
ControlInfoMap sensorInfoMap_;
BlackLevel blackLevel_;
+ static constexpr unsigned int kGammaLookupSize = 1024;
+ std::array<uint8_t, kGammaLookupSize> gammaTable_;
+ int lastBlackLevel_ = -1;
+
int32_t exposureMin_, exposureMax_;
int32_t exposure_;
double againMin_, againMax_, againMinStep_;
@@ -240,27 +247,61 @@ void IPASoftSimple::stop()
void IPASoftSimple::processStats(const ControlList &sensorControls)
{
+ SwIspStats::Histogram histogram = stats_->yHistogram;
+ if (ignoreUpdates_ > 0)
+ blackLevel_.update(histogram);
+ const uint8_t blackLevel = blackLevel_.get();
+
+ /*
+ * Black level must be subtracted to get the correct AWB ratios, they
+ * would be off if they were computed from the whole brightness range
+ * rather than from the sensor range.
+ */
+ const uint64_t nPixels = std::accumulate(
+ histogram.begin(), histogram.end(), 0);
+ const uint64_t offset = blackLevel * nPixels;
+ const uint64_t sumR = stats_->sumR_ - offset / 4;
+ const uint64_t sumG = stats_->sumG_ - offset / 2;
+ const uint64_t sumB = stats_->sumB_ - offset / 4;
+
/*
* Calculate red and blue gains for AWB.
* Clamp max gain at 4.0, this also avoids 0 division.
+ * Gain: 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
*/
- if (stats_->sumR_ <= stats_->sumG_ / 4)
- params_->gainR = 1024;
- else
- params_->gainR = 256 * stats_->sumG_ / stats_->sumR_;
+ const unsigned int gainR = sumR <= sumG / 4 ? 1024 : 256 * sumG / sumR;
+ const unsigned int gainB = sumB <= sumG / 4 ? 1024 : 256 * sumG / sumB;
+ /* Green gain and gamma values are fixed */
+ constexpr unsigned int gainG = 256;
+
+ /* Update the gamma table if needed */
+ if (blackLevel != lastBlackLevel_) {
+ constexpr float gamma = 0.5;
+ const unsigned int blackIndex = blackLevel * kGammaLookupSize / 256;
+ std::fill(gammaTable_.begin(), gammaTable_.begin() + blackIndex, 0);
+ const float divisor = kGammaLookupSize - blackIndex - 1.0;
+ for (unsigned int i = blackIndex; i < kGammaLookupSize; i++)
+ gammaTable_[i] = UINT8_MAX *
+ std::pow((i - blackIndex) / divisor, gamma);
+
+ lastBlackLevel_ = blackLevel;
+ }
- if (stats_->sumB_ <= stats_->sumG_ / 4)
- params_->gainB = 1024;
- else
- params_->gainB = 256 * stats_->sumG_ / stats_->sumB_;
+ for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {
+ constexpr unsigned int div =
+ DebayerParams::kRGBLookupSize * 256 / kGammaLookupSize;
+ unsigned int idx;
- /* Green gain and gamma values are fixed */
- params_->gainG = 256;
- params_->gamma = 0.5;
+ /* Apply gamma after gain! */
+ idx = std::min({ i * gainR / div, (kGammaLookupSize - 1) });
+ params_->red[i] = gammaTable_[idx];
- if (ignoreUpdates_ > 0)
- blackLevel_.update(stats_->yHistogram);
- params_->blackLevel = blackLevel_.get();
+ idx = std::min({ i * gainG / div, (kGammaLookupSize - 1) });
+ params_->green[i] = gammaTable_[idx];
+
+ idx = std::min({ i * gainB / div, (kGammaLookupSize - 1) });
+ params_->blue[i] = gammaTable_[idx];
+ }
setIspParams.emit();
@@ -281,7 +322,7 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
* https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
*/
const unsigned int blackLevelHistIdx =
- params_->blackLevel / (256 / SwIspStats::kYHistogramSize);
+ blackLevel / (256 / SwIspStats::kYHistogramSize);
const unsigned int histogramSize =
SwIspStats::kYHistogramSize - blackLevelHistIdx;
const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;
@@ -329,8 +370,8 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
LOG(IPASoft, Debug) << "exposureMSV " << exposureMSV
<< " exp " << exposure_ << " again " << again_
- << " gain R/B " << params_->gainR << "/" << params_->gainB
- << " black level " << params_->blackLevel;
+ << " gain R/B " << gainR << "/" << gainB
+ << " black level " << static_cast<unsigned int>(blackLevel);
}
void IPASoftSimple::updateExposure(double exposureMSV)
diff --git a/src/ipa/vimc/meson.build b/src/ipa/vimc/meson.build
index 264a2d9a..d0b63edd 100644
--- a/src/ipa/vimc/meson.build
+++ b/src/ipa/vimc/meson.build
@@ -5,9 +5,8 @@ ipa_name = 'ipa_vimc'
mod = shared_module(ipa_name,
['vimc.cpp', libcamera_generated_ipa_headers],
name_prefix : '',
- include_directories : [ipa_includes, libipa_includes],
- dependencies : libcamera_private,
- link_with : libipa,
+ include_directories : [ipa_includes],
+ dependencies : [libcamera_private, libipa_dep],
install : true,
install_dir : ipa_install_dir)
diff --git a/src/libcamera/control_ids_core.yaml b/src/libcamera/control_ids_core.yaml
index bf1f1a83..9d413a94 100644
--- a/src/libcamera/control_ids_core.yaml
+++ b/src/libcamera/control_ids_core.yaml
@@ -865,4 +865,11 @@ controls:
description: |
This is a long exposure image.
+ - Gamma:
+ type: float
+ description: |
+ Specify a fixed gamma value. Default must be 2.2 which closely mimics
+ sRGB gamma. Note that this is camera gamma, so it is applied as
+ 1.0/gamma.
+
...
diff --git a/src/libcamera/dma_buf_allocator.cpp b/src/libcamera/dma_buf_allocator.cpp
new file mode 100644
index 00000000..c06eca7d
--- /dev/null
+++ b/src/libcamera/dma_buf_allocator.cpp
@@ -0,0 +1,246 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ * Copyright (C) 2020, Raspberry Pi Ltd
+ *
+ * Helper class for dma-buf allocations.
+ */
+
+#include "libcamera/internal/dma_buf_allocator.h"
+
+#include <array>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <linux/dma-buf.h>
+#include <linux/dma-heap.h>
+#include <linux/udmabuf.h>
+
+#include <libcamera/base/log.h>
+
+/**
+ * \file dma_buf_allocator.cpp
+ * \brief dma-buf allocator
+ */
+
+namespace libcamera {
+
+#ifndef __DOXYGEN__
+struct DmaBufAllocatorInfo {
+ DmaBufAllocator::DmaBufAllocatorFlag type;
+ const char *deviceNodeName;
+};
+#endif
+
+static constexpr std::array<DmaBufAllocatorInfo, 4> providerInfos = { {
+ /*
+ * /dev/dma_heap/linux,cma is the CMA dma-heap. When the cma heap size is
+ * specified on the kernel command line, this gets renamed to "reserved".
+ */
+ { DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap, "/dev/dma_heap/linux,cma" },
+ { DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap, "/dev/dma_heap/reserved" },
+ { DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap, "/dev/dma_heap/system" },
+ { DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf, "/dev/udmabuf" },
+} };
+
+LOG_DEFINE_CATEGORY(DmaBufAllocator)
+
+/**
+ * \class DmaBufAllocator
+ * \brief Helper class for dma-buf allocations
+ *
+ * This class wraps a userspace dma-buf provider selected at construction time,
+ * and exposes functions to allocate dma-buffers from this provider.
+ *
+ * Different providers may provide dma-buffers with different properties for
+ * the underlying memory. Which providers are acceptable is specified through
+ * the type argument passed to the DmaBufAllocator() constructor.
+ */
+
+/**
+ * \enum DmaBufAllocator::DmaBufAllocatorFlag
+ * \brief Type of the dma-buf provider
+ * \var DmaBufAllocator::CmaHeap
+ * \brief Allocate from a CMA dma-heap, providing physically-contiguous memory
+ * \var DmaBufAllocator::SystemHeap
+ * \brief Allocate from the system dma-heap, using the page allocator
+ * \var DmaBufAllocator::UDmaBuf
+ * \brief Allocate using a memfd + /dev/udmabuf
+ */
+
+/**
+ * \typedef DmaBufAllocator::DmaBufAllocatorFlags
+ * \brief A bitwise combination of DmaBufAllocator::DmaBufAllocatorFlag values
+ */
+
+/**
+ * \brief Construct a DmaBufAllocator of a given type
+ * \param[in] type The type(s) of the dma-buf providers to allocate from
+ *
+ * The dma-buf provider type is selected with the \a type parameter, which
+ * defaults to the CMA heap. If no provider of the given type can be accessed,
+ * the constructed DmaBufAllocator instance is invalid as indicated by
+ * the isValid() function.
+ *
+ * Multiple types can be selected by combining type flags, in which case
+ * the constructed DmaBufAllocator will match one of the types. If multiple
+ * requested types can work on the system, which provider is used is undefined.
+ */
+DmaBufAllocator::DmaBufAllocator(DmaBufAllocatorFlags type)
+{
+ for (const auto &info : providerInfos) {
+ if (!(type & info.type))
+ continue;
+
+ int ret = ::open(info.deviceNodeName, O_RDWR | O_CLOEXEC, 0);
+ if (ret < 0) {
+ ret = errno;
+ LOG(DmaBufAllocator, Debug)
+ << "Failed to open " << info.deviceNodeName << ": "
+ << strerror(ret);
+ continue;
+ }
+
+ LOG(DmaBufAllocator, Debug) << "Using " << info.deviceNodeName;
+ providerHandle_ = UniqueFD(ret);
+ type_ = info.type;
+ break;
+ }
+
+ if (!providerHandle_.isValid())
+ LOG(DmaBufAllocator, Error) << "Could not open any dma-buf provider";
+}
+
+/**
+ * \brief Destroy the DmaBufAllocator instance
+ */
+DmaBufAllocator::~DmaBufAllocator() = default;
+
+/**
+ * \fn DmaBufAllocator::isValid()
+ * \brief Check if the DmaBufAllocator instance is valid
+ * \return True if the DmaBufAllocator is valid, false otherwise
+ */
+
+/* uClibc doesn't provide the file sealing API. */
+#ifndef __DOXYGEN__
+#if not HAVE_FILE_SEALS
+#define F_ADD_SEALS 1033
+#define F_SEAL_SHRINK 0x0002
+#endif
+#endif
+
+UniqueFD DmaBufAllocator::allocFromUDmaBuf(const char *name, std::size_t size)
+{
+ /* Size must be a multiple of the page size. Round it up. */
+ std::size_t pageMask = sysconf(_SC_PAGESIZE) - 1;
+ size = (size + pageMask) & ~pageMask;
+
+#if HAVE_MEMFD_CREATE
+ int ret = memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
+#else
+ int ret = syscall(SYS_memfd_create, name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
+#endif
+ if (ret < 0) {
+ ret = errno;
+ LOG(DmaBufAllocator, Error)
+ << "Failed to allocate memfd storage for " << name
+ << ": " << strerror(ret);
+ return {};
+ }
+
+ UniqueFD memfd(ret);
+
+ ret = ftruncate(memfd.get(), size);
+ if (ret < 0) {
+ ret = errno;
+ LOG(DmaBufAllocator, Error)
+ << "Failed to set memfd size for " << name
+ << ": " << strerror(ret);
+ return {};
+ }
+
+ /* udmabuf dma-buffers *must* have the F_SEAL_SHRINK seal. */
+ ret = fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_SHRINK);
+ if (ret < 0) {
+ ret = errno;
+ LOG(DmaBufAllocator, Error)
+ << "Failed to seal the memfd for " << name
+ << ": " << strerror(ret);
+ return {};
+ }
+
+ struct udmabuf_create create;
+
+ create.memfd = memfd.get();
+ create.flags = UDMABUF_FLAGS_CLOEXEC;
+ create.offset = 0;
+ create.size = size;
+
+ ret = ::ioctl(providerHandle_.get(), UDMABUF_CREATE, &create);
+ if (ret < 0) {
+ ret = errno;
+ LOG(DmaBufAllocator, Error)
+ << "Failed to create dma buf for " << name
+ << ": " << strerror(ret);
+ return {};
+ }
+
+ /* The underlying memfd is kept as as a reference in the kernel. */
+ return UniqueFD(ret);
+}
+
+UniqueFD DmaBufAllocator::allocFromHeap(const char *name, std::size_t size)
+{
+ struct dma_heap_allocation_data alloc = {};
+ int ret;
+
+ alloc.len = size;
+ alloc.fd_flags = O_CLOEXEC | O_RDWR;
+
+ ret = ::ioctl(providerHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc);
+ if (ret < 0) {
+ LOG(DmaBufAllocator, Error)
+ << "dma-heap allocation failure for " << name;
+ return {};
+ }
+
+ UniqueFD allocFd(alloc.fd);
+ ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name);
+ if (ret < 0) {
+ LOG(DmaBufAllocator, Error)
+ << "dma-heap naming failure for " << name;
+ return {};
+ }
+
+ return allocFd;
+}
+
+/**
+ * \brief Allocate a dma-buf from the DmaBufAllocator
+ * \param [in] name The name to set for the allocated buffer
+ * \param [in] size The size of the buffer to allocate
+ *
+ * Allocates a dma-buf with read/write access.
+ *
+ * If the allocation fails, return an invalid UniqueFD.
+ *
+ * \return The UniqueFD of the allocated buffer
+ */
+UniqueFD DmaBufAllocator::alloc(const char *name, std::size_t size)
+{
+ if (!name)
+ return {};
+
+ if (type_ == DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)
+ return allocFromUDmaBuf(name, size);
+ else
+ return allocFromHeap(name, size);
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/dma_heaps.cpp b/src/libcamera/dma_heaps.cpp
deleted file mode 100644
index d4cb880b..00000000
--- a/src/libcamera/dma_heaps.cpp
+++ /dev/null
@@ -1,165 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2020, Raspberry Pi Ltd
- *
- * Helper class for dma-heap allocations.
- */
-
-#include "libcamera/internal/dma_heaps.h"
-
-#include <array>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include <linux/dma-buf.h>
-#include <linux/dma-heap.h>
-
-#include <libcamera/base/log.h>
-
-/**
- * \file dma_heaps.cpp
- * \brief dma-heap allocator
- */
-
-namespace libcamera {
-
-/*
- * /dev/dma_heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma
- * to only have to worry about importing.
- *
- * Annoyingly, should the cma heap size be specified on the kernel command line
- * instead of DT, the heap gets named "reserved" instead.
- */
-
-#ifndef __DOXYGEN__
-struct DmaHeapInfo {
- DmaHeap::DmaHeapFlag type;
- const char *deviceNodeName;
-};
-#endif
-
-static constexpr std::array<DmaHeapInfo, 3> heapInfos = { {
- { DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/linux,cma" },
- { DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/reserved" },
- { DmaHeap::DmaHeapFlag::System, "/dev/dma_heap/system" },
-} };
-
-LOG_DEFINE_CATEGORY(DmaHeap)
-
-/**
- * \class DmaHeap
- * \brief Helper class for dma-heap allocations
- *
- * DMA heaps are kernel devices that provide an API to allocate memory from
- * different pools called "heaps", wrap each allocated piece of memory in a
- * dmabuf object, and return the dmabuf file descriptor to userspace. Multiple
- * heaps can be provided by the system, with different properties for the
- * underlying memory.
- *
- * This class wraps a DMA heap selected at construction time, and exposes
- * functions to manage memory allocation.
- */
-
-/**
- * \enum DmaHeap::DmaHeapFlag
- * \brief Type of the dma-heap
- * \var DmaHeap::Cma
- * \brief Allocate from a CMA dma-heap, providing physically-contiguous memory
- * \var DmaHeap::System
- * \brief Allocate from the system dma-heap, using the page allocator
- */
-
-/**
- * \typedef DmaHeap::DmaHeapFlags
- * \brief A bitwise combination of DmaHeap::DmaHeapFlag values
- */
-
-/**
- * \brief Construct a DmaHeap of a given type
- * \param[in] type The type(s) of the dma-heap(s) to allocate from
- *
- * The DMA heap type is selected with the \a type parameter, which defaults to
- * the CMA heap. If no heap of the given type can be accessed, the constructed
- * DmaHeap instance is invalid as indicated by the isValid() function.
- *
- * Multiple types can be selected by combining type flags, in which case the
- * constructed DmaHeap will match one of the types. If the system provides
- * multiple heaps that match the requested types, which heap is used is
- * undefined.
- */
-DmaHeap::DmaHeap(DmaHeapFlags type)
-{
- for (const auto &info : heapInfos) {
- if (!(type & info.type))
- continue;
-
- int ret = ::open(info.deviceNodeName, O_RDWR | O_CLOEXEC, 0);
- if (ret < 0) {
- ret = errno;
- LOG(DmaHeap, Debug)
- << "Failed to open " << info.deviceNodeName << ": "
- << strerror(ret);
- continue;
- }
-
- LOG(DmaHeap, Debug) << "Using " << info.deviceNodeName;
- dmaHeapHandle_ = UniqueFD(ret);
- break;
- }
-
- if (!dmaHeapHandle_.isValid())
- LOG(DmaHeap, Error) << "Could not open any dmaHeap device";
-}
-
-/**
- * \brief Destroy the DmaHeap instance
- */
-DmaHeap::~DmaHeap() = default;
-
-/**
- * \fn DmaHeap::isValid()
- * \brief Check if the DmaHeap instance is valid
- * \return True if the DmaHeap is valid, false otherwise
- */
-
-/**
- * \brief Allocate a dma-buf from the DmaHeap
- * \param [in] name The name to set for the allocated buffer
- * \param [in] size The size of the buffer to allocate
- *
- * Allocates a dma-buf with read/write access.
- *
- * If the allocation fails, return an invalid UniqueFD.
- *
- * \return The UniqueFD of the allocated buffer
- */
-UniqueFD DmaHeap::alloc(const char *name, std::size_t size)
-{
- int ret;
-
- if (!name)
- return {};
-
- struct dma_heap_allocation_data alloc = {};
-
- alloc.len = size;
- alloc.fd_flags = O_CLOEXEC | O_RDWR;
-
- ret = ::ioctl(dmaHeapHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc);
- if (ret < 0) {
- LOG(DmaHeap, Error) << "dmaHeap allocation failure for " << name;
- return {};
- }
-
- UniqueFD allocFd(alloc.fd);
- ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name);
- if (ret < 0) {
- LOG(DmaHeap, Error) << "dmaHeap naming failure for " << name;
- return {};
- }
-
- return allocFd;
-}
-
-} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index a3b12bc1..89504cee 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -15,7 +15,7 @@ libcamera_sources = files([
'delayed_controls.cpp',
'device_enumerator.cpp',
'device_enumerator_sysfs.cpp',
- 'dma_heaps.cpp',
+ 'dma_buf_allocator.cpp',
'fence.cpp',
'formats.cpp',
'framebuffer.cpp',
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
index 289af516..3041fd1e 100644
--- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
@@ -496,8 +496,6 @@ PipelineHandlerBase::generateConfiguration(Camera *camera, Span<const StreamRole
config->addConfiguration(cfg);
}
- config->validate();
-
return config;
}
diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
index 37fb310f..4a89e35f 100644
--- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp
+++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
@@ -12,7 +12,7 @@
#include <libcamera/formats.h>
#include "libcamera/internal/device_enumerator.h"
-#include "libcamera/internal/dma_heaps.h"
+#include "libcamera/internal/dma_buf_allocator.h"
#include "../common/pipeline_base.h"
#include "../common/rpi_stream.h"
@@ -86,7 +86,7 @@ public:
RPi::Device<Isp, 4> isp_;
/* DMAHEAP allocation helper. */
- DmaHeap dmaHeap_;
+ DmaBufAllocator dmaHeap_;
SharedFD lsTable_;
struct Config {
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index db3575c3..eb36578e 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -13,8 +13,8 @@
#include <memory>
#include <queue>
#include <set>
-#include <string>
#include <string.h>
+#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
@@ -38,7 +38,6 @@
#include "libcamera/internal/v4l2_subdevice.h"
#include "libcamera/internal/v4l2_videodevice.h"
-
namespace libcamera {
LOG_DEFINE_CATEGORY(SimplePipeline)
diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp
index c7650432..0ec9928e 100644
--- a/src/libcamera/pipeline/vimc/vimc.cpp
+++ b/src/libcamera/pipeline/vimc/vimc.cpp
@@ -114,6 +114,9 @@ static const std::map<PixelFormat, uint32_t> pixelformats{
{ formats::BGR888, MEDIA_BUS_FMT_RGB888_1X24 },
};
+static constexpr Size kMinSize{ 16, 16 };
+static constexpr Size kMaxSize{ 4096, 2160 };
+
} /* namespace */
VimcCameraConfiguration::VimcCameraConfiguration(VimcCameraData *data)
@@ -153,14 +156,20 @@ CameraConfiguration::Status VimcCameraConfiguration::validate()
const Size size = cfg.size;
/*
- * The scaler hardcodes a x3 scale-up ratio, and the sensor output size
- * is aligned to two pixels in both directions. The output width and
- * height thus have to be multiples of 6.
+ * The sensor output size is aligned to two pixels in both directions.
+ * Additionally, prior to v5.16, the scaler hardcodes a x3 scale-up
+ * ratio, requiring the output width and height to be multiples of 6.
*/
- cfg.size.width = std::max(48U, std::min(4096U, cfg.size.width));
- cfg.size.height = std::max(48U, std::min(2160U, cfg.size.height));
- cfg.size.width -= cfg.size.width % 6;
- cfg.size.height -= cfg.size.height % 6;
+ Size minSize{ kMinSize };
+ unsigned int alignment = 2;
+
+ if (data_->media_->version() < KERNEL_VERSION(5, 16, 0)) {
+ minSize *= 3;
+ alignment *= 3;
+ }
+
+ cfg.size.expandTo(minSize).boundTo(kMaxSize)
+ .alignDownTo(alignment, alignment);
if (cfg.size != size) {
LOG(VIMC, Debug)
@@ -216,10 +225,12 @@ PipelineHandlerVimc::generateConfiguration(Camera *camera,
}
}
- /* The scaler hardcodes a x3 scale-up ratio. */
- std::vector<SizeRange> sizes{
- SizeRange{ { 48, 48 }, { 4096, 2160 } }
- };
+ /* Prior to v5.16, the scaler hardcodes a x3 scale-up ratio. */
+ Size minSize{ kMinSize };
+ if (data->media_->version() < KERNEL_VERSION(5, 16, 0))
+ minSize *= 3;
+
+ std::vector<SizeRange> sizes{ { minSize, kMaxSize } };
formats[pixelformat.first] = sizes;
}
@@ -242,10 +253,18 @@ int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config)
StreamConfiguration &cfg = config->at(0);
int ret;
- /* The scaler hardcodes a x3 scale-up ratio. */
+ /*
+ * Prior to v5.16, the scaler hardcodes a x3 scale-up ratio. For newer
+ * kernels, use a sensor resolution of 1920x1080 and let the scaler
+ * produce the requested stream size.
+ */
+ Size sensorSize{ 1920, 1080 };
+ if (data->media_->version() < KERNEL_VERSION(5, 16, 0))
+ sensorSize = { cfg.size.width / 3, cfg.size.height / 3 };
+
V4L2SubdeviceFormat subformat = {};
subformat.code = MEDIA_BUS_FMT_SGRBG8_1X8;
- subformat.size = { cfg.size.width / 3, cfg.size.height / 3 };
+ subformat.size = sensorSize;
ret = data->sensor_->setFormat(&subformat);
if (ret)
@@ -293,7 +312,7 @@ int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config)
* vimc driver will fail pipeline validation.
*/
format.fourcc = V4L2PixelFormat(V4L2_PIX_FMT_SGRBG8);
- format.size = { cfg.size.width / 3, cfg.size.height / 3 };
+ format.size = sensorSize;
ret = data->raw_->setFormat(&format);
if (ret)
diff --git a/src/libcamera/software_isp/TODO b/src/libcamera/software_isp/TODO
index 4fcee39b..6bdc5905 100644
--- a/src/libcamera/software_isp/TODO
+++ b/src/libcamera/software_isp/TODO
@@ -72,19 +72,6 @@ stats in hardware, such as the i.MX7), but please keep it on your radar.
---
-4. Hide internal representation of gains from callers
-
-> struct DebayerParams {
-> static constexpr unsigned int kGain10 = 256;
-
-Forcing the caller to deal with the internal representation of gains
-isn't nice, especially given that it precludes implementing gains of
-different precisions in different backend. Wouldn't it be better to pass
-the values as floating point numbers, and convert them to the internal
-representation in the implementation of process() before using them ?
-
----
-
5. Store ISP parameters in per-frame buffers
> /**
diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
index efe75ea8..f4a299d5 100644
--- a/src/libcamera/software_isp/debayer.cpp
+++ b/src/libcamera/software_isp/debayer.cpp
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2023, Linaro Ltd
- * Copyright (C) 2023, Red Hat Inc.
+ * Copyright (C) 2023, 2024 Red Hat Inc.
*
* Authors:
* Hans de Goede <hdegoede@redhat.com>
@@ -19,34 +19,28 @@ namespace libcamera {
*/
/**
- * \var DebayerParams::kGain10
- * \brief const value for 1.0 gain
+ * \var DebayerParams::kRGBLookupSize
+ * \brief Size of a color lookup table
*/
/**
- * \var DebayerParams::gainR
- * \brief Red gain
- *
- * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
+ * \typedef DebayerParams::ColorLookupTable
+ * \brief Type of the lookup tables for red, green, blue values
*/
/**
- * \var DebayerParams::gainG
- * \brief Green gain
- *
- * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
+ * \var DebayerParams::red
+ * \brief Lookup table for red color, mapping input values to output values
*/
/**
- * \var DebayerParams::gainB
- * \brief Blue gain
- *
- * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
+ * \var DebayerParams::green
+ * \brief Lookup table for green color, mapping input values to output values
*/
/**
- * \var DebayerParams::gamma
- * \brief Gamma correction, 1.0 is no correction
+ * \var DebayerParams::blue
+ * \brief Lookup table for blue color, mapping input values to output values
*/
/**
diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
index 8254bbe9..f8d2677d 100644
--- a/src/libcamera/software_isp/debayer_cpu.cpp
+++ b/src/libcamera/software_isp/debayer_cpu.cpp
@@ -11,7 +11,6 @@
#include "debayer_cpu.h"
-#include <math.h>
#include <stdlib.h>
#include <time.h>
@@ -35,7 +34,7 @@ namespace libcamera {
* \param[in] stats Pointer to the stats object to use
*/
DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
- : stats_(std::move(stats)), gammaCorrection_(1.0), blackLevel_(0)
+ : stats_(std::move(stats))
{
/*
* Reading from uncached buffers may be very slow.
@@ -47,9 +46,9 @@ DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
*/
enableInputMemcpy_ = true;
- /* Initialize gamma to 1.0 curve */
- for (unsigned int i = 0; i < kGammaLookupSize; i++)
- gamma_[i] = i / (kGammaLookupSize / kRGBLookupSize);
+ /* Initialize color lookup tables */
+ for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++)
+ red_[i] = green_[i] = blue_[i] = i;
for (unsigned int i = 0; i < kMaxLineBuffers; i++)
lineBuffers_[i] = nullptr;
@@ -75,6 +74,8 @@ DebayerCpu::~DebayerCpu()
*dst++ = blue_[curr[x] / (div)]; \
*dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \
*dst++ = red_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \
+ if constexpr (addAlphaByte) \
+ *dst++ = 255; \
x++;
/*
@@ -86,6 +87,8 @@ DebayerCpu::~DebayerCpu()
*dst++ = blue_[(prev[x] + next[x]) / (2 * (div))]; \
*dst++ = green_[curr[x] / (div)]; \
*dst++ = red_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \
+ if constexpr (addAlphaByte) \
+ *dst++ = 255; \
x++;
/*
@@ -97,6 +100,8 @@ DebayerCpu::~DebayerCpu()
*dst++ = blue_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \
*dst++ = green_[curr[x] / (div)]; \
*dst++ = red_[(prev[x] + next[x]) / (2 * (div))]; \
+ if constexpr (addAlphaByte) \
+ *dst++ = 255; \
x++;
/*
@@ -108,8 +113,11 @@ DebayerCpu::~DebayerCpu()
*dst++ = blue_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \
*dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \
*dst++ = red_[curr[x] / (div)]; \
+ if constexpr (addAlphaByte) \
+ *dst++ = 255; \
x++;
+template<bool addAlphaByte>
void DebayerCpu::debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint8_t)
@@ -120,6 +128,7 @@ void DebayerCpu::debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
+template<bool addAlphaByte>
void DebayerCpu::debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint8_t)
@@ -130,6 +139,7 @@ void DebayerCpu::debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
+template<bool addAlphaByte>
void DebayerCpu::debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint16_t)
@@ -141,6 +151,7 @@ void DebayerCpu::debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
+template<bool addAlphaByte>
void DebayerCpu::debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint16_t)
@@ -152,6 +163,7 @@ void DebayerCpu::debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
+template<bool addAlphaByte>
void DebayerCpu::debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint16_t)
@@ -163,6 +175,7 @@ void DebayerCpu::debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
+template<bool addAlphaByte>
void DebayerCpu::debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
{
DECLARE_SRC_POINTERS(uint16_t)
@@ -174,6 +187,7 @@ void DebayerCpu::debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
+template<bool addAlphaByte>
void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
{
const int widthInBytes = window_.width * 5 / 4;
@@ -199,6 +213,7 @@ void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
+template<bool addAlphaByte>
void DebayerCpu::debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
{
const int widthInBytes = window_.width * 5 / 4;
@@ -219,6 +234,7 @@ void DebayerCpu::debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
+template<bool addAlphaByte>
void DebayerCpu::debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[])
{
const int widthInBytes = window_.width * 5 / 4;
@@ -239,6 +255,7 @@ void DebayerCpu::debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[])
}
}
+template<bool addAlphaByte>
void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
{
const int widthInBytes = window_.width * 5 / 4;
@@ -281,7 +298,12 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
config.bpp = (bayerFormat.bitDepth + 7) & ~7;
config.patternSize.width = 2;
config.patternSize.height = 2;
- config.outputFormats = std::vector<PixelFormat>({ formats::RGB888, formats::BGR888 });
+ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888,
+ formats::XRGB8888,
+ formats::ARGB8888,
+ formats::BGR888,
+ formats::XBGR8888,
+ formats::ABGR8888 });
return 0;
}
@@ -291,7 +313,12 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
config.bpp = 10;
config.patternSize.width = 4; /* 5 bytes per *4* pixels */
config.patternSize.height = 2;
- config.outputFormats = std::vector<PixelFormat>({ formats::RGB888, formats::BGR888 });
+ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888,
+ formats::XRGB8888,
+ formats::ARGB8888,
+ formats::BGR888,
+ formats::XBGR8888,
+ formats::ABGR8888 });
return 0;
}
@@ -307,6 +334,12 @@ int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &c
return 0;
}
+ if (outputFormat == formats::XRGB8888 || outputFormat == formats::ARGB8888 ||
+ outputFormat == formats::XBGR8888 || outputFormat == formats::ABGR8888) {
+ config.bpp = 32;
+ return 0;
+ }
+
LOG(Debayer, Info)
<< "Unsupported output format " << outputFormat.toString();
return -EINVAL;
@@ -342,6 +375,7 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF
{
BayerFormat bayerFormat =
BayerFormat::fromPixelFormat(inputFormat);
+ bool addAlphaByte = false;
xShift_ = 0;
swapRedBlueGains_ = false;
@@ -352,8 +386,16 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF
};
switch (outputFormat) {
+ case formats::XRGB8888:
+ case formats::ARGB8888:
+ addAlphaByte = true;
+ [[fallthrough]];
case formats::RGB888:
break;
+ case formats::XBGR8888:
+ case formats::ABGR8888:
+ addAlphaByte = true;
+ [[fallthrough]];
case formats::BGR888:
/* Swap R and B in bayer order to generate BGR888 instead of RGB888 */
swapRedBlueGains_ = true;
@@ -384,16 +426,16 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF
isStandardBayerOrder(bayerFormat.order)) {
switch (bayerFormat.bitDepth) {
case 8:
- debayer0_ = &DebayerCpu::debayer8_BGBG_BGR888;
- debayer1_ = &DebayerCpu::debayer8_GRGR_BGR888;
+ debayer0_ = addAlphaByte ? &DebayerCpu::debayer8_BGBG_BGR888<true> : &DebayerCpu::debayer8_BGBG_BGR888<false>;
+ debayer1_ = addAlphaByte ? &DebayerCpu::debayer8_GRGR_BGR888<true> : &DebayerCpu::debayer8_GRGR_BGR888<false>;
break;
case 10:
- debayer0_ = &DebayerCpu::debayer10_BGBG_BGR888;
- debayer1_ = &DebayerCpu::debayer10_GRGR_BGR888;
+ debayer0_ = addAlphaByte ? &DebayerCpu::debayer10_BGBG_BGR888<true> : &DebayerCpu::debayer10_BGBG_BGR888<false>;
+ debayer1_ = addAlphaByte ? &DebayerCpu::debayer10_GRGR_BGR888<true> : &DebayerCpu::debayer10_GRGR_BGR888<false>;
break;
case 12:
- debayer0_ = &DebayerCpu::debayer12_BGBG_BGR888;
- debayer1_ = &DebayerCpu::debayer12_GRGR_BGR888;
+ debayer0_ = addAlphaByte ? &DebayerCpu::debayer12_BGBG_BGR888<true> : &DebayerCpu::debayer12_BGBG_BGR888<false>;
+ debayer1_ = addAlphaByte ? &DebayerCpu::debayer12_GRGR_BGR888<true> : &DebayerCpu::debayer12_GRGR_BGR888<false>;
break;
}
setupStandardBayerOrder(bayerFormat.order);
@@ -404,20 +446,20 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF
bayerFormat.packing == BayerFormat::Packing::CSI2) {
switch (bayerFormat.order) {
case BayerFormat::BGGR:
- debayer0_ = &DebayerCpu::debayer10P_BGBG_BGR888;
- debayer1_ = &DebayerCpu::debayer10P_GRGR_BGR888;
+ debayer0_ = addAlphaByte ? &DebayerCpu::debayer10P_BGBG_BGR888<true> : &DebayerCpu::debayer10P_BGBG_BGR888<false>;
+ debayer1_ = addAlphaByte ? &DebayerCpu::debayer10P_GRGR_BGR888<true> : &DebayerCpu::debayer10P_GRGR_BGR888<false>;
return 0;
case BayerFormat::GBRG:
- debayer0_ = &DebayerCpu::debayer10P_GBGB_BGR888;
- debayer1_ = &DebayerCpu::debayer10P_RGRG_BGR888;
+ debayer0_ = addAlphaByte ? &DebayerCpu::debayer10P_GBGB_BGR888<true> : &DebayerCpu::debayer10P_GBGB_BGR888<false>;
+ debayer1_ = addAlphaByte ? &DebayerCpu::debayer10P_RGRG_BGR888<true> : &DebayerCpu::debayer10P_RGRG_BGR888<false>;
return 0;
case BayerFormat::GRBG:
- debayer0_ = &DebayerCpu::debayer10P_GRGR_BGR888;
- debayer1_ = &DebayerCpu::debayer10P_BGBG_BGR888;
+ debayer0_ = addAlphaByte ? &DebayerCpu::debayer10P_GRGR_BGR888<true> : &DebayerCpu::debayer10P_GRGR_BGR888<false>;
+ debayer1_ = addAlphaByte ? &DebayerCpu::debayer10P_BGBG_BGR888<true> : &DebayerCpu::debayer10P_BGBG_BGR888<false>;
return 0;
case BayerFormat::RGGB:
- debayer0_ = &DebayerCpu::debayer10P_RGRG_BGR888;
- debayer1_ = &DebayerCpu::debayer10P_GBGB_BGR888;
+ debayer0_ = addAlphaByte ? &DebayerCpu::debayer10P_RGRG_BGR888<true> : &DebayerCpu::debayer10P_RGRG_BGR888<false>;
+ debayer1_ = addAlphaByte ? &DebayerCpu::debayer10P_GBGB_BGR888<true> : &DebayerCpu::debayer10P_GBGB_BGR888<false>;
return 0;
default:
break;
@@ -698,37 +740,9 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams
clock_gettime(CLOCK_MONOTONIC_RAW, &frameStartTime);
}
- /* Apply DebayerParams */
- if (params.gamma != gammaCorrection_ || params.blackLevel != blackLevel_) {
- const unsigned int blackIndex =
- params.blackLevel * kGammaLookupSize / 256;
- std::fill(gamma_.begin(), gamma_.begin() + blackIndex, 0);
- const float divisor = kGammaLookupSize - blackIndex - 1.0;
- for (unsigned int i = blackIndex; i < kGammaLookupSize; i++)
- gamma_[i] = UINT8_MAX * powf((i - blackIndex) / divisor, params.gamma);
-
- gammaCorrection_ = params.gamma;
- blackLevel_ = params.blackLevel;
- }
-
- if (swapRedBlueGains_)
- std::swap(params.gainR, params.gainB);
-
- for (unsigned int i = 0; i < kRGBLookupSize; i++) {
- constexpr unsigned int div =
- kRGBLookupSize * DebayerParams::kGain10 / kGammaLookupSize;
- unsigned int idx;
-
- /* Apply gamma after gain! */
- idx = std::min({ i * params.gainR / div, (kGammaLookupSize - 1) });
- red_[i] = gamma_[idx];
-
- idx = std::min({ i * params.gainG / div, (kGammaLookupSize - 1) });
- green_[i] = gamma_[idx];
-
- idx = std::min({ i * params.gainB / div, (kGammaLookupSize - 1) });
- blue_[i] = gamma_[idx];
- }
+ green_ = params.green;
+ red_ = swapRedBlueGains_ ? params.blue : params.red;
+ blue_ = swapRedBlueGains_ ? params.red : params.blue;
/* Copy metadata from the input buffer */
FrameMetadata &metadata = output->_d()->metadata();
diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
index de216fe3..1dac6435 100644
--- a/src/libcamera/software_isp/debayer_cpu.h
+++ b/src/libcamera/software_isp/debayer_cpu.h
@@ -85,18 +85,28 @@ private:
using debayerFn = void (DebayerCpu::*)(uint8_t *dst, const uint8_t *src[]);
/* 8-bit raw bayer format */
+ template<bool addAlphaByte>
void debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
+ template<bool addAlphaByte>
void debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
/* unpacked 10-bit raw bayer format */
+ template<bool addAlphaByte>
void debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
+ template<bool addAlphaByte>
void debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
/* unpacked 12-bit raw bayer format */
+ template<bool addAlphaByte>
void debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
+ template<bool addAlphaByte>
void debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
/* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */
+ template<bool addAlphaByte>
void debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
+ template<bool addAlphaByte>
void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
+ template<bool addAlphaByte>
void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
+ template<bool addAlphaByte>
void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
struct DebayerInputConfig {
@@ -122,15 +132,12 @@ private:
void process2(const uint8_t *src, uint8_t *dst);
void process4(const uint8_t *src, uint8_t *dst);
- static constexpr unsigned int kGammaLookupSize = 1024;
- static constexpr unsigned int kRGBLookupSize = 256;
/* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */
static constexpr unsigned int kMaxLineBuffers = 5;
- std::array<uint8_t, kGammaLookupSize> gamma_;
- std::array<uint8_t, kRGBLookupSize> red_;
- std::array<uint8_t, kRGBLookupSize> green_;
- std::array<uint8_t, kRGBLookupSize> blue_;
+ DebayerParams::ColorLookupTable red_;
+ DebayerParams::ColorLookupTable green_;
+ DebayerParams::ColorLookupTable blue_;
debayerFn debayer0_;
debayerFn debayer1_;
debayerFn debayer2_;
@@ -146,8 +153,6 @@ private:
unsigned int xShift_; /* Offset of 0/1 applied to window_.x */
bool enableInputMemcpy_;
bool swapRedBlueGains_;
- float gammaCorrection_;
- unsigned int blackLevel_;
unsigned int measuredFrames_;
int64_t frameProcessTime_;
/* Skip 30 frames for things to stabilize then measure 30 frames */
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index c9b6be56..20fb6f48 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -7,6 +7,8 @@
#include "libcamera/internal/software_isp/software_isp.h"
+#include <cmath>
+#include <stdint.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
@@ -18,6 +20,7 @@
#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/mapped_framebuffer.h"
+#include "libcamera/internal/software_isp/debayer_params.h"
#include "debayer_cpu.h"
@@ -63,12 +66,29 @@ LOG_DEFINE_CATEGORY(SoftwareIsp)
* handler
*/
SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor)
- : debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10,
- DebayerParams::kGain10, 0.5f, 0 },
- dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)
+ : dmaHeap_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap |
+ DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |
+ DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)
{
+ /*
+ * debayerParams_ must be initialized because the initial value is used for
+ * the first two frames, i.e. until stats processing starts providing its
+ * own parameters.
+ *
+ * \todo This should be handled in the same place as the related
+ * operations, in the IPA module.
+ */
+ std::array<uint8_t, 256> gammaTable;
+ for (unsigned int i = 0; i < 256; i++)
+ gammaTable[i] = UINT8_MAX * std::pow(i / 256.0, 0.5);
+ for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {
+ debayerParams_.red[i] = gammaTable[i];
+ debayerParams_.green[i] = gammaTable[i];
+ debayerParams_.blue[i] = gammaTable[i];
+ }
+
if (!dmaHeap_.isValid()) {
- LOG(SoftwareIsp, Error) << "Failed to create DmaHeap object";
+ LOG(SoftwareIsp, Error) << "Failed to create DmaBufAllocator object";
return;
}
diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
index 6da77775..82824433 100644
--- a/src/libcamera/v4l2_subdevice.cpp
+++ b/src/libcamera/v4l2_subdevice.cpp
@@ -1366,8 +1366,62 @@ void routeToKernel(const V4L2Subdevice::Route &route,
kroute.flags = route.flags;
}
+/*
+ * Legacy routing support for pre-v6.10-rc1 kernels. Drop when v6.12-rc1 gets
+ * released.
+ */
+struct v4l2_subdev_routing_legacy {
+ __u32 which;
+ __u32 num_routes;
+ __u64 routes;
+ __u32 reserved[6];
+};
+
+#define VIDIOC_SUBDEV_G_ROUTING_LEGACY _IOWR('V', 38, struct v4l2_subdev_routing_legacy)
+#define VIDIOC_SUBDEV_S_ROUTING_LEGACY _IOWR('V', 39, struct v4l2_subdev_routing_legacy)
+
} /* namespace */
+int V4L2Subdevice::getRoutingLegacy(Routing *routing, Whence whence)
+{
+ struct v4l2_subdev_routing_legacy rt = {};
+
+ rt.which = whence;
+
+ int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING_LEGACY, &rt);
+ if (ret == 0 || ret == -ENOTTY)
+ return ret;
+
+ if (ret != -ENOSPC) {
+ LOG(V4L2, Error)
+ << "Failed to retrieve number of routes: "
+ << strerror(-ret);
+ return ret;
+ }
+
+ std::vector<struct v4l2_subdev_route> routes{ rt.num_routes };
+ rt.routes = reinterpret_cast<uintptr_t>(routes.data());
+
+ ret = ioctl(VIDIOC_SUBDEV_G_ROUTING_LEGACY, &rt);
+ if (ret) {
+ LOG(V4L2, Error)
+ << "Failed to retrieve routes: " << strerror(-ret);
+ return ret;
+ }
+
+ if (rt.num_routes != routes.size()) {
+ LOG(V4L2, Error) << "Invalid number of routes";
+ return -EINVAL;
+ }
+
+ routing->resize(rt.num_routes);
+
+ for (const auto &[i, route] : utils::enumerate(routes))
+ routeFromKernel((*routing)[i], route);
+
+ return 0;
+}
+
/**
* \brief Retrieve the subdevice's internal routing table
* \param[out] routing The routing table
@@ -1388,19 +1442,25 @@ int V4L2Subdevice::getRouting(Routing *routing, Whence whence)
rt.which = whence;
int ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
- if (ret == 0 || ret == -ENOTTY)
- return ret;
+ if (ret == -ENOTTY)
+ return V4L2Subdevice::getRoutingLegacy(routing, whence);
- if (ret != -ENOSPC) {
+ if (ret) {
LOG(V4L2, Error)
<< "Failed to retrieve number of routes: "
<< strerror(-ret);
return ret;
}
+ if (!rt.num_routes)
+ return 0;
+
std::vector<struct v4l2_subdev_route> routes{ rt.num_routes };
rt.routes = reinterpret_cast<uintptr_t>(routes.data());
+ rt.len_routes = rt.num_routes;
+ rt.num_routes = 0;
+
ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
if (ret) {
LOG(V4L2, Error)
@@ -1421,6 +1481,33 @@ int V4L2Subdevice::getRouting(Routing *routing, Whence whence)
return 0;
}
+int V4L2Subdevice::setRoutingLegacy(Routing *routing, Whence whence)
+{
+ std::vector<struct v4l2_subdev_route> routes{ routing->size() };
+
+ for (const auto &[i, route] : utils::enumerate(*routing))
+ routeToKernel(route, routes[i]);
+
+ struct v4l2_subdev_routing_legacy rt = {};
+ rt.which = whence;
+ rt.num_routes = routes.size();
+ rt.routes = reinterpret_cast<uintptr_t>(routes.data());
+
+ int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING_LEGACY, &rt);
+ if (ret) {
+ LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret);
+ return ret;
+ }
+
+ routes.resize(rt.num_routes);
+ routing->resize(rt.num_routes);
+
+ for (const auto &[i, route] : utils::enumerate(routes))
+ routeFromKernel((*routing)[i], route);
+
+ return 0;
+}
+
/**
* \brief Set a routing table on the V4L2 subdevice
* \param[inout] routing The routing table
@@ -1447,16 +1534,43 @@ int V4L2Subdevice::setRouting(Routing *routing, Whence whence)
struct v4l2_subdev_routing rt = {};
rt.which = whence;
+ rt.len_routes = routes.size();
rt.num_routes = routes.size();
rt.routes = reinterpret_cast<uintptr_t>(routes.data());
int ret = ioctl(VIDIOC_SUBDEV_S_ROUTING, &rt);
+ if (ret == -ENOTTY)
+ return setRoutingLegacy(routing, whence);
+
if (ret) {
LOG(V4L2, Error) << "Failed to set routes: " << strerror(-ret);
return ret;
}
- routes.resize(rt.num_routes);
+ /*
+ * The kernel may want to return more routes than we have space for. In
+ * that event, we must issue a VIDIOC_SUBDEV_G_ROUTING call to retrieve
+ * the additional routes.
+ */
+ if (rt.num_routes > routes.size()) {
+ routes.resize(rt.num_routes);
+
+ rt.len_routes = rt.num_routes;
+ rt.num_routes = 0;
+
+ ret = ioctl(VIDIOC_SUBDEV_G_ROUTING, &rt);
+ if (ret) {
+ LOG(V4L2, Error)
+ << "Failed to retrieve routes: " << strerror(-ret);
+ return ret;
+ }
+ }
+
+ if (rt.num_routes != routes.size()) {
+ LOG(V4L2, Error) << "Invalid number of routes";
+ return -EINVAL;
+ }
+
routing->resize(rt.num_routes);
for (const auto &[i, route] : utils::enumerate(routes))
diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
index 55f81916..025006bc 100644
--- a/src/libcamera/yaml_parser.cpp
+++ b/src/libcamera/yaml_parser.cpp
@@ -104,7 +104,7 @@ std::size_t YamlObject::size() const
*/
/**
- * \fn template<typename T> YamlObject::get<T>(const T &defaultValue) const
+ * \fn template<typename T, typename U> YamlObject::get<T>(U &&defaultValue) const
* \brief Parse the YamlObject as a \a T value
* \param[in] defaultValue The default value when failing to parse
*
@@ -118,14 +118,15 @@ std::size_t YamlObject::size() const
#ifndef __DOXYGEN__
template<>
-std::optional<bool> YamlObject::get() const
+std::optional<bool>
+YamlObject::Getter<bool>::get(const YamlObject &obj) const
{
- if (type_ != Type::Value)
+ if (obj.type_ != Type::Value)
return std::nullopt;
- if (value_ == "true")
+ if (obj.value_ == "true")
return true;
- else if (value_ == "false")
+ else if (obj.value_ == "false")
return false;
return std::nullopt;
@@ -182,14 +183,15 @@ bool parseUnsignedInteger(const std::string &str, unsigned long max,
} /* namespace */
template<>
-std::optional<int8_t> YamlObject::get() const
+std::optional<int8_t>
+YamlObject::Getter<int8_t>::get(const YamlObject &obj) const
{
- if (type_ != Type::Value)
+ if (obj.type_ != Type::Value)
return std::nullopt;
long value;
- if (!parseSignedInteger(value_, std::numeric_limits<int8_t>::min(),
+ if (!parseSignedInteger(obj.value_, std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max(), &value))
return std::nullopt;
@@ -197,14 +199,15 @@ std::optional<int8_t> YamlObject::get() const
}
template<>
-std::optional<uint8_t> YamlObject::get() const
+std::optional<uint8_t>
+YamlObject::Getter<uint8_t>::get(const YamlObject &obj) const
{
- if (type_ != Type::Value)
+ if (obj.type_ != Type::Value)
return std::nullopt;
unsigned long value;
- if (!parseUnsignedInteger(value_, std::numeric_limits<uint8_t>::max(),
+ if (!parseUnsignedInteger(obj.value_, std::numeric_limits<uint8_t>::max(),
&value))
return std::nullopt;
@@ -212,14 +215,15 @@ std::optional<uint8_t> YamlObject::get() const
}
template<>
-std::optional<int16_t> YamlObject::get() const
+std::optional<int16_t>
+YamlObject::Getter<int16_t>::get(const YamlObject &obj) const
{
- if (type_ != Type::Value)
+ if (obj.type_ != Type::Value)
return std::nullopt;
long value;
- if (!parseSignedInteger(value_, std::numeric_limits<int16_t>::min(),
+ if (!parseSignedInteger(obj.value_, std::numeric_limits<int16_t>::min(),
std::numeric_limits<int16_t>::max(), &value))
return std::nullopt;
@@ -227,14 +231,15 @@ std::optional<int16_t> YamlObject::get() const
}
template<>
-std::optional<uint16_t> YamlObject::get() const
+std::optional<uint16_t>
+YamlObject::Getter<uint16_t>::get(const YamlObject &obj) const
{
- if (type_ != Type::Value)
+ if (obj.type_ != Type::Value)
return std::nullopt;
unsigned long value;
- if (!parseUnsignedInteger(value_, std::numeric_limits<uint16_t>::max(),
+ if (!parseUnsignedInteger(obj.value_, std::numeric_limits<uint16_t>::max(),
&value))
return std::nullopt;
@@ -242,14 +247,15 @@ std::optional<uint16_t> YamlObject::get() const
}
template<>
-std::optional<int32_t> YamlObject::get() const
+std::optional<int32_t>
+YamlObject::Getter<int32_t>::get(const YamlObject &obj) const
{
- if (type_ != Type::Value)
+ if (obj.type_ != Type::Value)
return std::nullopt;
long value;
- if (!parseSignedInteger(value_, std::numeric_limits<int32_t>::min(),
+ if (!parseSignedInteger(obj.value_, std::numeric_limits<int32_t>::min(),
std::numeric_limits<int32_t>::max(), &value))
return std::nullopt;
@@ -257,14 +263,15 @@ std::optional<int32_t> YamlObject::get() const
}
template<>
-std::optional<uint32_t> YamlObject::get() const
+std::optional<uint32_t>
+YamlObject::Getter<uint32_t>::get(const YamlObject &obj) const
{
- if (type_ != Type::Value)
+ if (obj.type_ != Type::Value)
return std::nullopt;
unsigned long value;
- if (!parseUnsignedInteger(value_, std::numeric_limits<uint32_t>::max(),
+ if (!parseUnsignedInteger(obj.value_, std::numeric_limits<uint32_t>::max(),
&value))
return std::nullopt;
@@ -272,18 +279,26 @@ std::optional<uint32_t> YamlObject::get() const
}
template<>
-std::optional<double> YamlObject::get() const
+std::optional<float>
+YamlObject::Getter<float>::get(const YamlObject &obj) const
{
- if (type_ != Type::Value)
+ return obj.get<double>();
+}
+
+template<>
+std::optional<double>
+YamlObject::Getter<double>::get(const YamlObject &obj) const
+{
+ if (obj.type_ != Type::Value)
return std::nullopt;
- if (value_ == "")
+ if (obj.value_ == "")
return std::nullopt;
char *end;
errno = 0;
- double value = utils::strtod(value_.c_str(), &end);
+ double value = utils::strtod(obj.value_.c_str(), &end);
if ('\0' != *end || errno == ERANGE)
return std::nullopt;
@@ -292,28 +307,30 @@ std::optional<double> YamlObject::get() const
}
template<>
-std::optional<std::string> YamlObject::get() const
+std::optional<std::string>
+YamlObject::Getter<std::string>::get(const YamlObject &obj) const
{
- if (type_ != Type::Value)
+ if (obj.type_ != Type::Value)
return std::nullopt;
- return value_;
+ return obj.value_;
}
template<>
-std::optional<Size> YamlObject::get() const
+std::optional<Size>
+YamlObject::Getter<Size>::get(const YamlObject &obj) const
{
- if (type_ != Type::List)
+ if (obj.type_ != Type::List)
return std::nullopt;
- if (list_.size() != 2)
+ if (obj.list_.size() != 2)
return std::nullopt;
- auto width = list_[0].value->get<uint32_t>();
+ auto width = obj.list_[0].value->get<uint32_t>();
if (!width)
return std::nullopt;
- auto height = list_[1].value->get<uint32_t>();
+ auto height = obj.list_[1].value->get<uint32_t>();
if (!height)
return std::nullopt;
@@ -339,6 +356,7 @@ std::optional<Size> YamlObject::get() const
template<typename T,
std::enable_if_t<
std::is_same_v<bool, T> ||
+ std::is_same_v<float, T> ||
std::is_same_v<double, T> ||
std::is_same_v<int8_t, T> ||
std::is_same_v<uint8_t, T> ||
@@ -367,6 +385,7 @@ std::optional<std::vector<T>> YamlObject::getList() const
}
template std::optional<std::vector<bool>> YamlObject::getList<bool>() const;
+template std::optional<std::vector<float>> YamlObject::getList<float>() const;
template std::optional<std::vector<double>> YamlObject::getList<double>() const;
template std::optional<std::vector<int8_t>> YamlObject::getList<int8_t>() const;
template std::optional<std::vector<uint8_t>> YamlObject::getList<uint8_t>() const;
@@ -468,10 +487,13 @@ bool YamlObject::contains(const std::string &key) const
*/
const YamlObject &YamlObject::operator[](const std::string &key) const
{
- if (type_ != Type::Dictionary || !contains(key))
+ if (type_ != Type::Dictionary)
return empty;
auto iter = dictionary_.find(key);
+ if (iter == dictionary_.end())
+ return empty;
+
return *iter->second;
}
diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp
index 8e2b7e92..bd016cbd 100644
--- a/src/v4l2/v4l2_compat.cpp
+++ b/src/v4l2/v4l2_compat.cpp
@@ -7,6 +7,7 @@
#include "v4l2_compat_manager.h"
+#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
@@ -28,12 +29,25 @@ using namespace libcamera;
va_end(ap); \
}
+namespace {
+
+/*
+ * Determine if the flags require a further mode arguments that needs to be
+ * parsed from va_args.
+ */
+bool needs_mode(int flags)
+{
+ return (flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE);
+}
+
+} /* namespace */
+
extern "C" {
LIBCAMERA_PUBLIC int open(const char *path, int oflag, ...)
{
mode_t mode = 0;
- if (oflag & O_CREAT || oflag & O_TMPFILE)
+ if (needs_mode(oflag))
extract_va_arg(mode_t, mode, oflag);
return V4L2CompatManager::instance()->openat(AT_FDCWD, path,
@@ -43,6 +57,7 @@ LIBCAMERA_PUBLIC int open(const char *path, int oflag, ...)
/* _FORTIFY_SOURCE redirects open to __open_2 */
LIBCAMERA_PUBLIC int __open_2(const char *path, int oflag)
{
+ assert(!needs_mode(oflag));
return open(path, oflag);
}
@@ -50,7 +65,7 @@ LIBCAMERA_PUBLIC int __open_2(const char *path, int oflag)
LIBCAMERA_PUBLIC int open64(const char *path, int oflag, ...)
{
mode_t mode = 0;
- if (oflag & O_CREAT || oflag & O_TMPFILE)
+ if (needs_mode(oflag))
extract_va_arg(mode_t, mode, oflag);
return V4L2CompatManager::instance()->openat(AT_FDCWD, path,
@@ -59,14 +74,15 @@ LIBCAMERA_PUBLIC int open64(const char *path, int oflag, ...)
LIBCAMERA_PUBLIC int __open64_2(const char *path, int oflag)
{
- return open(path, oflag);
+ assert(!needs_mode(oflag));
+ return open64(path, oflag);
}
#endif
LIBCAMERA_PUBLIC int openat(int dirfd, const char *path, int oflag, ...)
{
mode_t mode = 0;
- if (oflag & O_CREAT || oflag & O_TMPFILE)
+ if (needs_mode(oflag))
extract_va_arg(mode_t, mode, oflag);
return V4L2CompatManager::instance()->openat(dirfd, path, oflag, mode);
@@ -74,6 +90,7 @@ LIBCAMERA_PUBLIC int openat(int dirfd, const char *path, int oflag, ...)
LIBCAMERA_PUBLIC int __openat_2(int dirfd, const char *path, int oflag)
{
+ assert(!needs_mode(oflag));
return openat(dirfd, path, oflag);
}
@@ -81,7 +98,7 @@ LIBCAMERA_PUBLIC int __openat_2(int dirfd, const char *path, int oflag)
LIBCAMERA_PUBLIC int openat64(int dirfd, const char *path, int oflag, ...)
{
mode_t mode = 0;
- if (oflag & O_CREAT || oflag & O_TMPFILE)
+ if (needs_mode(oflag))
extract_va_arg(mode_t, mode, oflag);
return V4L2CompatManager::instance()->openat(dirfd, path,
@@ -90,7 +107,8 @@ LIBCAMERA_PUBLIC int openat64(int dirfd, const char *path, int oflag, ...)
LIBCAMERA_PUBLIC int __openat64_2(int dirfd, const char *path, int oflag)
{
- return openat(dirfd, path, oflag);
+ assert(!needs_mode(oflag));
+ return openat64(dirfd, path, oflag);
}
#endif