[1/4] libcamera: Copy Vector class files from libipa
diff mbox series

Message ID 20250206141018.236272-2-stefan.klug@ideasonboard.com
State Accepted
Commit e506b45822472c23368809f4bfe0289826c5756a
Headers show
Series
  • Move Vector class from libipa to libcamera
Related show

Commit Message

Stefan Klug Feb. 6, 2025, 2:10 p.m. UTC
Prepare the move of the Vector class from libipa to libcamera by copying
the relevant files into the corresponding libcamera directories. The
files are copied without modification.

Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
---
 include/libcamera/internal/vector.h | 370 ++++++++++++++++++++++++++++
 src/libcamera/vector.cpp            | 351 ++++++++++++++++++++++++++
 test/vector.cpp                     | 100 ++++++++
 3 files changed, 821 insertions(+)
 create mode 100644 include/libcamera/internal/vector.h
 create mode 100644 src/libcamera/vector.cpp
 create mode 100644 test/vector.cpp

Comments

Kieran Bingham Feb. 11, 2025, 9:38 a.m. UTC | #1
Quoting Stefan Klug (2025-02-06 14:10:08)
> Prepare the move of the Vector class from libipa to libcamera by copying
> the relevant files into the corresponding libcamera directories. The
> files are copied without modification.

No modification makes this easy:

Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

But I think there are a few checkstyle issues in this file. It
might be a good time to clean the file up as we're here.

29ce46bb9f92ed742df64305e68afee811ef7605 libcamera: Copy Vector class files from libipa
---------------------------------------------------------------------------------------
Header include/libcamera/internal/vector.h added without corresponding update to include/libcamera/internal/meson.build
--- include/libcamera/internal/vector.h
+++ include/libcamera/internal/vector.h
@@ -179,52 +179,88 @@
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
 #endif /* __DOXYGEN__ */
-	constexpr const T &x() const { return data_[0]; }
+	constexpr const T &x() const
+	{
+		return data_[0];
+	}
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
 #endif /* __DOXYGEN__ */
-	constexpr const T &y() const { return data_[1]; }
+	constexpr const T &y() const
+	{
+		return data_[1];
+	}
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
 #endif /* __DOXYGEN__ */
-	constexpr const T &z() const { return data_[2]; }
+	constexpr const T &z() const
+	{
+		return data_[2];
+	}
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
 #endif /* __DOXYGEN__ */
-	constexpr T &x() { return data_[0]; }
+	constexpr T &x()
+	{
+		return data_[0];
+	}
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
 #endif /* __DOXYGEN__ */
-	constexpr T &y() { return data_[1]; }
+	constexpr T &y()
+	{
+		return data_[1];
+	}
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
 #endif /* __DOXYGEN__ */
-	constexpr T &z() { return data_[2]; }
+	constexpr T &z()
+	{
+		return data_[2];
+	}
 
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
 #endif /* __DOXYGEN__ */
-	constexpr const T &r() const { return data_[0]; }
+	constexpr const T &r() const
+	{
+		return data_[0];
+	}
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
 #endif /* __DOXYGEN__ */
-	constexpr const T &g() const { return data_[1]; }
+	constexpr const T &g() const
+	{
+		return data_[1];
+	}
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
 #endif /* __DOXYGEN__ */
-	constexpr const T &b() const { return data_[2]; }
+	constexpr const T &b() const
+	{
+		return data_[2];
+	}
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
 #endif /* __DOXYGEN__ */
-	constexpr T &r() { return data_[0]; }
+	constexpr T &r()
+	{
+		return data_[0];
+	}
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
 #endif /* __DOXYGEN__ */
-	constexpr T &g() { return data_[1]; }
+	constexpr T &g()
+	{
+		return data_[1];
+	}
 #ifndef __DOXYGEN__
 	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
 #endif /* __DOXYGEN__ */
-	constexpr T &b() { return data_[2]; }
+	constexpr T &b()
+	{
+		return data_[2];
+	}
 
 	constexpr double length2() const
 	{
--- test/vector.cpp
+++ test/vector.cpp
@@ -14,12 +14,12 @@
 
 using namespace libcamera::ipa;
 
-#define ASSERT_EQ(a, b)							\
-if ((a) != (b)) {							\
-	std::cout << #a " != " #b << " (line " << __LINE__ << ")"	\
-		  << std::endl;						\
-	return TestFail;						\
-}
+#define ASSERT_EQ(a, b)                                                   \
+	if ((a) != (b)) {                                                 \
+		std::cout << #a " != " #b << " (line " << __LINE__ << ")" \
+			  << std::endl;                                   \
+		return TestFail;                                          \
+	}
 
 class VectorTest : public Test
 {
@@ -35,7 +35,7 @@
 		ASSERT_EQ(v1.length(), 0.0);
 		ASSERT_EQ(v1.length2(), 0.0);
 
-		Vector<double, 3> v2{{ 1.0, 4.0, 8.0 }};
+		Vector<double, 3> v2{ { 1.0, 4.0, 8.0 } };
 
 		ASSERT_EQ(v2[0], 1.0);
 		ASSERT_EQ(v2[1], 4.0);
@@ -57,41 +57,41 @@
 
 		ASSERT_EQ(v2, v3);
 
-		v3 = Vector<double, 3>{{ 4.0, 4.0, 4.0 }};
+		v3 = Vector<double, 3>{ { 4.0, 4.0, 4.0 } };
 
-		ASSERT_EQ(v2 + v3, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
-		ASSERT_EQ(v2 + 4.0, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
-		ASSERT_EQ(v2 - v3, (Vector<double, 3>{{ -3.0, 0.0, 4.0 }}));
-		ASSERT_EQ(v2 - 4.0, (Vector<double, 3>{{ -3.0, 0.0, 4.0 }}));
-		ASSERT_EQ(v2 * v3, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
-		ASSERT_EQ(v2 * 4.0, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
-		ASSERT_EQ(v2 / v3, (Vector<double, 3>{{ 0.25, 1.0, 2.0 }}));
-		ASSERT_EQ(v2 / 4.0, (Vector<double, 3>{{ 0.25, 1.0, 2.0 }}));
+		ASSERT_EQ(v2 + v3, (Vector<double, 3>{ { 5.0, 8.0, 12.0 } }));
+		ASSERT_EQ(v2 + 4.0, (Vector<double, 3>{ { 5.0, 8.0, 12.0 } }));
+		ASSERT_EQ(v2 - v3, (Vector<double, 3>{ { -3.0, 0.0, 4.0 } }));
+		ASSERT_EQ(v2 - 4.0, (Vector<double, 3>{ { -3.0, 0.0, 4.0 } }));
+		ASSERT_EQ(v2 * v3, (Vector<double, 3>{ { 4.0, 16.0, 32.0 } }));
+		ASSERT_EQ(v2 * 4.0, (Vector<double, 3>{ { 4.0, 16.0, 32.0 } }));
+		ASSERT_EQ(v2 / v3, (Vector<double, 3>{ { 0.25, 1.0, 2.0 } }));
+		ASSERT_EQ(v2 / 4.0, (Vector<double, 3>{ { 0.25, 1.0, 2.0 } }));
 
-		ASSERT_EQ(v2.min(v3), (Vector<double, 3>{{ 1.0, 4.0, 4.0 }}));
-		ASSERT_EQ(v2.min(4.0), (Vector<double, 3>{{ 1.0, 4.0, 4.0 }}));
-		ASSERT_EQ(v2.max(v3), (Vector<double, 3>{{ 4.0, 4.0, 8.0 }}));
-		ASSERT_EQ(v2.max(4.0), (Vector<double, 3>{{ 4.0, 4.0, 8.0 }}));
+		ASSERT_EQ(v2.min(v3), (Vector<double, 3>{ { 1.0, 4.0, 4.0 } }));
+		ASSERT_EQ(v2.min(4.0), (Vector<double, 3>{ { 1.0, 4.0, 4.0 } }));
+		ASSERT_EQ(v2.max(v3), (Vector<double, 3>{ { 4.0, 4.0, 8.0 } }));
+		ASSERT_EQ(v2.max(4.0), (Vector<double, 3>{ { 4.0, 4.0, 8.0 } }));
 
 		ASSERT_EQ(v2.dot(v3), 52.0);
 
 		v2 += v3;
-		ASSERT_EQ(v2, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
+		ASSERT_EQ(v2, (Vector<double, 3>{ { 5.0, 8.0, 12.0 } }));
 		v2 -= v3;
-		ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+		ASSERT_EQ(v2, (Vector<double, 3>{ { 1.0, 4.0, 8.0 } }));
 		v2 *= v3;
-		ASSERT_EQ(v2, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
+		ASSERT_EQ(v2, (Vector<double, 3>{ { 4.0, 16.0, 32.0 } }));
 		v2 /= v3;
-		ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+		ASSERT_EQ(v2, (Vector<double, 3>{ { 1.0, 4.0, 8.0 } }));
 
 		v2 += 4.0;
-		ASSERT_EQ(v2, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
+		ASSERT_EQ(v2, (Vector<double, 3>{ { 5.0, 8.0, 12.0 } }));
 		v2 -= 4.0;
-		ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+		ASSERT_EQ(v2, (Vector<double, 3>{ { 1.0, 4.0, 8.0 } }));
 		v2 *= 4.0;
-		ASSERT_EQ(v2, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
+		ASSERT_EQ(v2, (Vector<double, 3>{ { 4.0, 16.0, 32.0 } }));
 		v2 /= 4.0;
-		ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+		ASSERT_EQ(v2, (Vector<double, 3>{ { 1.0, 4.0, 8.0 } }));
 
 		return TestPass;
 	}
---
5 potential issues detected, please review

To reduce future checkstyle noise, I'd be fine applying that diff to
this commit as part of the move (documenting that only
checkstyle/formatting updates are made during the copy).




> 
> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> ---
>  include/libcamera/internal/vector.h | 370 ++++++++++++++++++++++++++++
>  src/libcamera/vector.cpp            | 351 ++++++++++++++++++++++++++
>  test/vector.cpp                     | 100 ++++++++
>  3 files changed, 821 insertions(+)
>  create mode 100644 include/libcamera/internal/vector.h
>  create mode 100644 src/libcamera/vector.cpp
>  create mode 100644 test/vector.cpp
> 
> diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h
> new file mode 100644
> index 000000000000..fe33c9d6fbd1
> --- /dev/null
> +++ b/include/libcamera/internal/vector.h
> @@ -0,0 +1,370 @@
> +/* 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 <functional>
> +#include <numeric>
> +#include <optional>
> +#include <ostream>
> +
> +#include <libcamera/base/log.h>
> +#include <libcamera/base/span.h>
> +
> +#include "libcamera/internal/matrix.h"
> +#include "libcamera/internal/yaml_parser.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 explicit Vector(T scalar)
> +       {
> +               data_.fill(scalar);
> +       }
> +
> +       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];
> +       }
> +
> +       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 operator+(const Vector &other) const
> +       {
> +               return apply(*this, other, std::plus<>{});
> +       }
> +
> +       constexpr Vector operator+(T scalar) const
> +       {
> +               return apply(*this, scalar, std::plus<>{});
> +       }
> +
> +       constexpr Vector operator-(const Vector &other) const
> +       {
> +               return apply(*this, other, std::minus<>{});
> +       }
> +
> +       constexpr Vector operator-(T scalar) const
> +       {
> +               return apply(*this, scalar, std::minus<>{});
> +       }
> +
> +       constexpr Vector operator*(const Vector &other) const
> +       {
> +               return apply(*this, other, std::multiplies<>{});
> +       }
> +
> +       constexpr Vector operator*(T scalar) const
> +       {
> +               return apply(*this, scalar, std::multiplies<>{});
> +       }
> +
> +       constexpr Vector operator/(const Vector &other) const
> +       {
> +               return apply(*this, other, std::divides<>{});
> +       }
> +
> +       constexpr Vector operator/(T scalar) const
> +       {
> +               return apply(*this, scalar, std::divides<>{});
> +       }
> +
> +       Vector &operator+=(const Vector &other)
> +       {
> +               return apply(other, [](T a, T b) { return a + b; });
> +       }
> +
> +       Vector &operator+=(T scalar)
> +       {
> +               return apply(scalar, [](T a, T b) { return a + b; });
> +       }
> +
> +       Vector &operator-=(const Vector &other)
> +       {
> +               return apply(other, [](T a, T b) { return a - b; });
> +       }
> +
> +       Vector &operator-=(T scalar)
> +       {
> +               return apply(scalar, [](T a, T b) { return a - b; });
> +       }
> +
> +       Vector &operator*=(const Vector &other)
> +       {
> +               return apply(other, [](T a, T b) { return a * b; });
> +       }
> +
> +       Vector &operator*=(T scalar)
> +       {
> +               return apply(scalar, [](T a, T b) { return a * b; });
> +       }
> +
> +       Vector &operator/=(const Vector &other)
> +       {
> +               return apply(other, [](T a, T b) { return a / b; });
> +       }
> +
> +       Vector &operator/=(T scalar)
> +       {
> +               return apply(scalar, [](T a, T b) { return a / b; });
> +       }
> +
> +       constexpr Vector min(const Vector &other) const
> +       {
> +               return apply(*this, other, [](T a, T b) { return std::min(a, b); });
> +       }
> +
> +       constexpr Vector min(T scalar) const
> +       {
> +               return apply(*this, scalar, [](T a, T b) { return std::min(a, b); });
> +       }
> +
> +       constexpr Vector max(const Vector &other) const
> +       {
> +               return apply(*this, other, [](T a, T b) { return std::max(a, b); });
> +       }
> +
> +       constexpr Vector max(T scalar) const
> +       {
> +               return apply(*this, scalar, [](T a, T b) -> T { return std::max(a, b); });
> +       }
> +
> +       constexpr T dot(const Vector<T, Rows> &other) const
> +       {
> +               T ret = 0;
> +               for (unsigned int i = 0; i < Rows; i++)
> +                       ret += data_[i] * other[i];
> +               return ret;
> +       }
> +
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
> +#endif /* __DOXYGEN__ */
> +       constexpr const T &x() const { return data_[0]; }
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
> +#endif /* __DOXYGEN__ */
> +       constexpr const T &y() const { return data_[1]; }
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
> +#endif /* __DOXYGEN__ */
> +       constexpr const T &z() const { return data_[2]; }
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
> +#endif /* __DOXYGEN__ */
> +       constexpr T &x() { return data_[0]; }
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
> +#endif /* __DOXYGEN__ */
> +       constexpr T &y() { return data_[1]; }
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
> +#endif /* __DOXYGEN__ */
> +       constexpr T &z() { return data_[2]; }
> +
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
> +#endif /* __DOXYGEN__ */
> +       constexpr const T &r() const { return data_[0]; }
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
> +#endif /* __DOXYGEN__ */
> +       constexpr const T &g() const { return data_[1]; }
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
> +#endif /* __DOXYGEN__ */
> +       constexpr const T &b() const { return data_[2]; }
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
> +#endif /* __DOXYGEN__ */
> +       constexpr T &r() { return data_[0]; }
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
> +#endif /* __DOXYGEN__ */
> +       constexpr T &g() { return data_[1]; }
> +#ifndef __DOXYGEN__
> +       template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
> +#endif /* __DOXYGEN__ */
> +       constexpr T &b() { return data_[2]; }
> +
> +       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());
> +       }
> +
> +       template<typename R = T>
> +       constexpr R sum() const
> +       {
> +               return std::accumulate(data_.begin(), data_.end(), R{});
> +       }
> +
> +private:
> +       template<class BinaryOp>
> +       static constexpr Vector apply(const Vector &lhs, const Vector &rhs, BinaryOp op)
> +       {
> +               Vector result;
> +               std::transform(lhs.data_.begin(), lhs.data_.end(),
> +                              rhs.data_.begin(), result.data_.begin(),
> +                              op);
> +
> +               return result;
> +       }
> +
> +       template<class BinaryOp>
> +       static constexpr Vector apply(const Vector &lhs, T rhs, BinaryOp op)
> +       {
> +               Vector result;
> +               std::transform(lhs.data_.begin(), lhs.data_.end(),
> +                              result.data_.begin(),
> +                              [&op, rhs](T v) { return op(v, rhs); });
> +
> +               return result;
> +       }
> +
> +       template<class BinaryOp>
> +       Vector &apply(const Vector &other, BinaryOp op)
> +       {
> +               auto itOther = other.data_.begin();
> +               std::for_each(data_.begin(), data_.end(),
> +                             [&op, &itOther](T &v) { v = op(v, *itOther++); });
> +
> +               return *this;
> +       }
> +
> +       template<class BinaryOp>
> +       Vector &apply(T scalar, BinaryOp op)
> +       {
> +               std::for_each(data_.begin(), data_.end(),
> +                             [&op, scalar](T &v) { v = op(v, scalar); });
> +
> +               return *this;
> +       }
> +
> +       std::array<T, Rows> data_;
> +};
> +
> +template<typename T>
> +using RGB = Vector<T, 3>;
> +
> +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/libcamera/vector.cpp b/src/libcamera/vector.cpp
> new file mode 100644
> index 000000000000..8019f8cfdc85
> --- /dev/null
> +++ b/src/libcamera/vector.cpp
> @@ -0,0 +1,351 @@
> +/* 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 an uninitialized vector
> + */
> +
> +/**
> + * \fn Vector::Vector(T scalar)
> + * \brief Construct a vector filled with a \a scalar value
> + * \param[in] scalar The scalar value
> + */
> +
> +/**
> + * \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::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 Calculate the sum of this vector and \a other element-wise
> + * \param[in] other The other vector
> + * \return The element-wise sum of this vector and \a other
> + */
> +
> +/**
> + * \fn Vector::operator+(T scalar) const
> + * \brief Calculate the sum of this vector and \a scalar element-wise
> + * \param[in] scalar The scalar
> + * \return The element-wise sum of this vector and \a other
> + */
> +
> +/**
> + * \fn Vector::operator-(Vector const &other) const
> + * \brief Calculate the difference of this vector and \a other element-wise
> + * \param[in] other The other vector
> + * \return The element-wise subtraction of \a other from this vector
> + */
> +
> +/**
> + * \fn Vector::operator-(T scalar) const
> + * \brief Calculate the difference of this vector and \a scalar element-wise
> + * \param[in] scalar The scalar
> + * \return The element-wise subtraction of \a scalar from this vector
> + */
> +
> +/**
> + * \fn Vector::operator*(const Vector &other) const
> + * \brief Calculate the product of this vector and \a other element-wise
> + * \param[in] other The other vector
> + * \return The element-wise product of this vector and \a other
> + */
> +
> +/**
> + * \fn Vector::operator*(T scalar) const
> + * \brief Calculate the product of this vector and \a scalar element-wise
> + * \param[in] scalar The scalar
> + * \return The element-wise product of this vector and \a scalar
> + */
> +
> +/**
> + * \fn Vector::operator/(const Vector &other) const
> + * \brief Calculate the quotient of this vector and \a other element-wise
> + * \param[in] other The other vector
> + * \return The element-wise division of this vector by \a other
> + */
> +
> +/**
> + * \fn Vector::operator/(T scalar) const
> + * \brief Calculate the quotient of this vector and \a scalar element-wise
> + * \param[in] scalar The scalar
> + * \return The element-wise division of this vector by \a scalar
> + */
> +
> +/**
> + * \fn Vector::operator+=(Vector const &other)
> + * \brief Add \a other element-wise to this vector
> + * \param[in] other The other vector
> + * \return This vector
> + */
> +
> +/**
> + * \fn Vector::operator+=(T scalar)
> + * \brief Add \a scalar element-wise to this vector
> + * \param[in] scalar The scalar
> + * \return This vector
> + */
> +
> +/**
> + * \fn Vector::operator-=(Vector const &other)
> + * \brief Subtract \a other element-wise from this vector
> + * \param[in] other The other vector
> + * \return This vector
> + */
> +
> +/**
> + * \fn Vector::operator-=(T scalar)
> + * \brief Subtract \a scalar element-wise from this vector
> + * \param[in] scalar The scalar
> + * \return This vector
> + */
> +
> +/**
> + * \fn Vector::operator*=(const Vector &other)
> + * \brief Multiply this vector by \a other element-wise
> + * \param[in] other The other vector
> + * \return This vector
> + */
> +
> +/**
> + * \fn Vector::operator*=(T scalar)
> + * \brief Multiply this vector by \a scalar element-wise
> + * \param[in] scalar The scalar
> + * \return This vector
> + */
> +
> +/**
> + * \fn Vector::operator/=(const Vector &other)
> + * \brief Divide this vector by \a other element-wise
> + * \param[in] other The other vector
> + * \return This vector
> + */
> +
> +/**
> + * \fn Vector::operator/=(T scalar)
> + * \brief Divide this vector by \a scalar element-wise
> + * \param[in] scalar The scalar
> + * \return This vector
> + */
> +
> +/**
> + * \fn Vector::min(const Vector &other) const
> + * \brief Calculate the minimum of this vector and \a other element-wise
> + * \param[in] other The other vector
> + * \return The element-wise minimum of this vector and \a other
> + */
> +
> +/**
> + * \fn Vector::min(T scalar) const
> + * \brief Calculate the minimum of this vector and \a scalar element-wise
> + * \param[in] scalar The scalar
> + * \return The element-wise minimum of this vector and \a scalar
> + */
> +
> +/**
> + * \fn Vector::max(const Vector &other) const
> + * \brief Calculate the maximum of this vector and \a other element-wise
> + * \param[in] other The other vector
> + * \return The element-wise maximum of this vector and \a other
> + */
> +
> +/**
> + * \fn Vector::max(T scalar) const
> + * \brief Calculate the maximum of this vector and \a scalar element-wise
> + * \param[in] scalar The scalar
> + * \return The element-wise maximum of this vector and \a scalar
> + */
> +
> +/**
> + * \fn Vector::dot(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 constexpr T &Vector::x()
> + * \brief Convenience function to access the first element of the vector
> + * \return The first element of the vector
> + */
> +
> +/**
> + * \fn constexpr T &Vector::y()
> + * \brief Convenience function to access the second element of the vector
> + * \return The second element of the vector
> + */
> +
> +/**
> + * \fn constexpr T &Vector::z()
> + * \brief Convenience function to access the third element of the vector
> + * \return The third element of the vector
> + */
> +
> +/**
> + * \fn constexpr const T &Vector::x() const
> + * \copydoc Vector::x()
> + */
> +
> +/**
> + * \fn constexpr const T &Vector::y() const
> + * \copydoc Vector::y()
> + */
> +
> +/**
> + * \fn constexpr const T &Vector::z() const
> + * \copydoc Vector::z()
> + */
> +
> +/**
> + * \fn constexpr T &Vector::r()
> + * \brief Convenience function to access the first element of the vector
> + * \return The first element of the vector
> + */
> +
> +/**
> + * \fn constexpr T &Vector::g()
> + * \brief Convenience function to access the second element of the vector
> + * \return The second element of the vector
> + */
> +
> +/**
> + * \fn constexpr T &Vector::b()
> + * \brief Convenience function to access the third element of the vector
> + * \return The third element of the vector
> + */
> +
> +/**
> + * \fn constexpr const T &Vector::r() const
> + * \copydoc Vector::r()
> + */
> +
> +/**
> + * \fn constexpr const T &Vector::g() const
> + * \copydoc Vector::g()
> + */
> +
> +/**
> + * \fn constexpr const T &Vector::b() const
> + * \copydoc Vector::b()
> + */
> +
> +/**
> + * \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::sum() const
> + * \brief Calculate the sum of all the vector elements
> + * \tparam R The type of the sum
> + *
> + * The type R of the sum defaults to the type T of the elements, but can be set
> + * explicitly to use a different type in case the type T would risk
> + * overflowing.
> + *
> + * \return The sum of all the vector elements
> + */
> +
> +/**
> + * \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
> + */
> +
> +/**
> + * \typedef RGB
> + * \brief A Vector of 3 elements representing an RGB pixel value
> + */
> +
> +/**
> + * \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/test/vector.cpp b/test/vector.cpp
> new file mode 100644
> index 000000000000..8e4ec77d7820
> --- /dev/null
> +++ b/test/vector.cpp
> @@ -0,0 +1,100 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2024, Ideas on Board Oy
> + *
> + * Vector tests
> + */
> +
> +#include "../src/ipa/libipa/vector.h"
> +
> +#include <cmath>
> +#include <iostream>
> +
> +#include "test.h"
> +
> +using namespace libcamera::ipa;
> +
> +#define ASSERT_EQ(a, b)                                                        \
> +if ((a) != (b)) {                                                      \
> +       std::cout << #a " != " #b << " (line " << __LINE__ << ")"       \
> +                 << std::endl;                                         \
> +       return TestFail;                                                \
> +}
> +
> +class VectorTest : public Test
> +{
> +protected:
> +       int run()
> +       {
> +               Vector<double, 3> v1{ 0.0 };
> +
> +               ASSERT_EQ(v1[0], 0.0);
> +               ASSERT_EQ(v1[1], 0.0);
> +               ASSERT_EQ(v1[2], 0.0);
> +
> +               ASSERT_EQ(v1.length(), 0.0);
> +               ASSERT_EQ(v1.length2(), 0.0);
> +
> +               Vector<double, 3> v2{{ 1.0, 4.0, 8.0 }};
> +
> +               ASSERT_EQ(v2[0], 1.0);
> +               ASSERT_EQ(v2[1], 4.0);
> +               ASSERT_EQ(v2[2], 8.0);
> +
> +               ASSERT_EQ(v2.x(), 1.0);
> +               ASSERT_EQ(v2.y(), 4.0);
> +               ASSERT_EQ(v2.z(), 8.0);
> +
> +               ASSERT_EQ(v2.r(), 1.0);
> +               ASSERT_EQ(v2.g(), 4.0);
> +               ASSERT_EQ(v2.b(), 8.0);
> +
> +               ASSERT_EQ(v2.length2(), 81.0);
> +               ASSERT_EQ(v2.length(), 9.0);
> +               ASSERT_EQ(v2.sum(), 13.0);
> +
> +               Vector<double, 3> v3{ v2 };
> +
> +               ASSERT_EQ(v2, v3);
> +
> +               v3 = Vector<double, 3>{{ 4.0, 4.0, 4.0 }};
> +
> +               ASSERT_EQ(v2 + v3, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
> +               ASSERT_EQ(v2 + 4.0, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
> +               ASSERT_EQ(v2 - v3, (Vector<double, 3>{{ -3.0, 0.0, 4.0 }}));
> +               ASSERT_EQ(v2 - 4.0, (Vector<double, 3>{{ -3.0, 0.0, 4.0 }}));
> +               ASSERT_EQ(v2 * v3, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
> +               ASSERT_EQ(v2 * 4.0, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
> +               ASSERT_EQ(v2 / v3, (Vector<double, 3>{{ 0.25, 1.0, 2.0 }}));
> +               ASSERT_EQ(v2 / 4.0, (Vector<double, 3>{{ 0.25, 1.0, 2.0 }}));
> +
> +               ASSERT_EQ(v2.min(v3), (Vector<double, 3>{{ 1.0, 4.0, 4.0 }}));
> +               ASSERT_EQ(v2.min(4.0), (Vector<double, 3>{{ 1.0, 4.0, 4.0 }}));
> +               ASSERT_EQ(v2.max(v3), (Vector<double, 3>{{ 4.0, 4.0, 8.0 }}));
> +               ASSERT_EQ(v2.max(4.0), (Vector<double, 3>{{ 4.0, 4.0, 8.0 }}));
> +
> +               ASSERT_EQ(v2.dot(v3), 52.0);
> +
> +               v2 += v3;
> +               ASSERT_EQ(v2, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
> +               v2 -= v3;
> +               ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
> +               v2 *= v3;
> +               ASSERT_EQ(v2, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
> +               v2 /= v3;
> +               ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
> +
> +               v2 += 4.0;
> +               ASSERT_EQ(v2, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
> +               v2 -= 4.0;
> +               ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
> +               v2 *= 4.0;
> +               ASSERT_EQ(v2, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
> +               v2 /= 4.0;
> +               ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
> +
> +               return TestPass;
> +       }
> +};
> +
> +TEST_REGISTER(VectorTest)
> -- 
> 2.43.0
>

Patch
diff mbox series

diff --git a/include/libcamera/internal/vector.h b/include/libcamera/internal/vector.h
new file mode 100644
index 000000000000..fe33c9d6fbd1
--- /dev/null
+++ b/include/libcamera/internal/vector.h
@@ -0,0 +1,370 @@ 
+/* 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 <functional>
+#include <numeric>
+#include <optional>
+#include <ostream>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/span.h>
+
+#include "libcamera/internal/matrix.h"
+#include "libcamera/internal/yaml_parser.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 explicit Vector(T scalar)
+	{
+		data_.fill(scalar);
+	}
+
+	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];
+	}
+
+	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 operator+(const Vector &other) const
+	{
+		return apply(*this, other, std::plus<>{});
+	}
+
+	constexpr Vector operator+(T scalar) const
+	{
+		return apply(*this, scalar, std::plus<>{});
+	}
+
+	constexpr Vector operator-(const Vector &other) const
+	{
+		return apply(*this, other, std::minus<>{});
+	}
+
+	constexpr Vector operator-(T scalar) const
+	{
+		return apply(*this, scalar, std::minus<>{});
+	}
+
+	constexpr Vector operator*(const Vector &other) const
+	{
+		return apply(*this, other, std::multiplies<>{});
+	}
+
+	constexpr Vector operator*(T scalar) const
+	{
+		return apply(*this, scalar, std::multiplies<>{});
+	}
+
+	constexpr Vector operator/(const Vector &other) const
+	{
+		return apply(*this, other, std::divides<>{});
+	}
+
+	constexpr Vector operator/(T scalar) const
+	{
+		return apply(*this, scalar, std::divides<>{});
+	}
+
+	Vector &operator+=(const Vector &other)
+	{
+		return apply(other, [](T a, T b) { return a + b; });
+	}
+
+	Vector &operator+=(T scalar)
+	{
+		return apply(scalar, [](T a, T b) { return a + b; });
+	}
+
+	Vector &operator-=(const Vector &other)
+	{
+		return apply(other, [](T a, T b) { return a - b; });
+	}
+
+	Vector &operator-=(T scalar)
+	{
+		return apply(scalar, [](T a, T b) { return a - b; });
+	}
+
+	Vector &operator*=(const Vector &other)
+	{
+		return apply(other, [](T a, T b) { return a * b; });
+	}
+
+	Vector &operator*=(T scalar)
+	{
+		return apply(scalar, [](T a, T b) { return a * b; });
+	}
+
+	Vector &operator/=(const Vector &other)
+	{
+		return apply(other, [](T a, T b) { return a / b; });
+	}
+
+	Vector &operator/=(T scalar)
+	{
+		return apply(scalar, [](T a, T b) { return a / b; });
+	}
+
+	constexpr Vector min(const Vector &other) const
+	{
+		return apply(*this, other, [](T a, T b) { return std::min(a, b); });
+	}
+
+	constexpr Vector min(T scalar) const
+	{
+		return apply(*this, scalar, [](T a, T b) { return std::min(a, b); });
+	}
+
+	constexpr Vector max(const Vector &other) const
+	{
+		return apply(*this, other, [](T a, T b) { return std::max(a, b); });
+	}
+
+	constexpr Vector max(T scalar) const
+	{
+		return apply(*this, scalar, [](T a, T b) -> T { return std::max(a, b); });
+	}
+
+	constexpr T dot(const Vector<T, Rows> &other) const
+	{
+		T ret = 0;
+		for (unsigned int i = 0; i < Rows; i++)
+			ret += data_[i] * other[i];
+		return ret;
+	}
+
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
+#endif /* __DOXYGEN__ */
+	constexpr const T &x() const { return data_[0]; }
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
+#endif /* __DOXYGEN__ */
+	constexpr const T &y() const { return data_[1]; }
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
+#endif /* __DOXYGEN__ */
+	constexpr const T &z() const { return data_[2]; }
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
+#endif /* __DOXYGEN__ */
+	constexpr T &x() { return data_[0]; }
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
+#endif /* __DOXYGEN__ */
+	constexpr T &y() { return data_[1]; }
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
+#endif /* __DOXYGEN__ */
+	constexpr T &z() { return data_[2]; }
+
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
+#endif /* __DOXYGEN__ */
+	constexpr const T &r() const { return data_[0]; }
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
+#endif /* __DOXYGEN__ */
+	constexpr const T &g() const { return data_[1]; }
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
+#endif /* __DOXYGEN__ */
+	constexpr const T &b() const { return data_[2]; }
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 1>>
+#endif /* __DOXYGEN__ */
+	constexpr T &r() { return data_[0]; }
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 2>>
+#endif /* __DOXYGEN__ */
+	constexpr T &g() { return data_[1]; }
+#ifndef __DOXYGEN__
+	template<bool Dependent = false, typename = std::enable_if_t<Dependent || Rows >= 3>>
+#endif /* __DOXYGEN__ */
+	constexpr T &b() { return data_[2]; }
+
+	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());
+	}
+
+	template<typename R = T>
+	constexpr R sum() const
+	{
+		return std::accumulate(data_.begin(), data_.end(), R{});
+	}
+
+private:
+	template<class BinaryOp>
+	static constexpr Vector apply(const Vector &lhs, const Vector &rhs, BinaryOp op)
+	{
+		Vector result;
+		std::transform(lhs.data_.begin(), lhs.data_.end(),
+			       rhs.data_.begin(), result.data_.begin(),
+			       op);
+
+		return result;
+	}
+
+	template<class BinaryOp>
+	static constexpr Vector apply(const Vector &lhs, T rhs, BinaryOp op)
+	{
+		Vector result;
+		std::transform(lhs.data_.begin(), lhs.data_.end(),
+			       result.data_.begin(),
+			       [&op, rhs](T v) { return op(v, rhs); });
+
+		return result;
+	}
+
+	template<class BinaryOp>
+	Vector &apply(const Vector &other, BinaryOp op)
+	{
+		auto itOther = other.data_.begin();
+		std::for_each(data_.begin(), data_.end(),
+			      [&op, &itOther](T &v) { v = op(v, *itOther++); });
+
+		return *this;
+	}
+
+	template<class BinaryOp>
+	Vector &apply(T scalar, BinaryOp op)
+	{
+		std::for_each(data_.begin(), data_.end(),
+			      [&op, scalar](T &v) { v = op(v, scalar); });
+
+		return *this;
+	}
+
+	std::array<T, Rows> data_;
+};
+
+template<typename T>
+using RGB = Vector<T, 3>;
+
+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/libcamera/vector.cpp b/src/libcamera/vector.cpp
new file mode 100644
index 000000000000..8019f8cfdc85
--- /dev/null
+++ b/src/libcamera/vector.cpp
@@ -0,0 +1,351 @@ 
+/* 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 an uninitialized vector
+ */
+
+/**
+ * \fn Vector::Vector(T scalar)
+ * \brief Construct a vector filled with a \a scalar value
+ * \param[in] scalar The scalar value
+ */
+
+/**
+ * \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::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 Calculate the sum of this vector and \a other element-wise
+ * \param[in] other The other vector
+ * \return The element-wise sum of this vector and \a other
+ */
+
+/**
+ * \fn Vector::operator+(T scalar) const
+ * \brief Calculate the sum of this vector and \a scalar element-wise
+ * \param[in] scalar The scalar
+ * \return The element-wise sum of this vector and \a other
+ */
+
+/**
+ * \fn Vector::operator-(Vector const &other) const
+ * \brief Calculate the difference of this vector and \a other element-wise
+ * \param[in] other The other vector
+ * \return The element-wise subtraction of \a other from this vector
+ */
+
+/**
+ * \fn Vector::operator-(T scalar) const
+ * \brief Calculate the difference of this vector and \a scalar element-wise
+ * \param[in] scalar The scalar
+ * \return The element-wise subtraction of \a scalar from this vector
+ */
+
+/**
+ * \fn Vector::operator*(const Vector &other) const
+ * \brief Calculate the product of this vector and \a other element-wise
+ * \param[in] other The other vector
+ * \return The element-wise product of this vector and \a other
+ */
+
+/**
+ * \fn Vector::operator*(T scalar) const
+ * \brief Calculate the product of this vector and \a scalar element-wise
+ * \param[in] scalar The scalar
+ * \return The element-wise product of this vector and \a scalar
+ */
+
+/**
+ * \fn Vector::operator/(const Vector &other) const
+ * \brief Calculate the quotient of this vector and \a other element-wise
+ * \param[in] other The other vector
+ * \return The element-wise division of this vector by \a other
+ */
+
+/**
+ * \fn Vector::operator/(T scalar) const
+ * \brief Calculate the quotient of this vector and \a scalar element-wise
+ * \param[in] scalar The scalar
+ * \return The element-wise division of this vector by \a scalar
+ */
+
+/**
+ * \fn Vector::operator+=(Vector const &other)
+ * \brief Add \a other element-wise to this vector
+ * \param[in] other The other vector
+ * \return This vector
+ */
+
+/**
+ * \fn Vector::operator+=(T scalar)
+ * \brief Add \a scalar element-wise to this vector
+ * \param[in] scalar The scalar
+ * \return This vector
+ */
+
+/**
+ * \fn Vector::operator-=(Vector const &other)
+ * \brief Subtract \a other element-wise from this vector
+ * \param[in] other The other vector
+ * \return This vector
+ */
+
+/**
+ * \fn Vector::operator-=(T scalar)
+ * \brief Subtract \a scalar element-wise from this vector
+ * \param[in] scalar The scalar
+ * \return This vector
+ */
+
+/**
+ * \fn Vector::operator*=(const Vector &other)
+ * \brief Multiply this vector by \a other element-wise
+ * \param[in] other The other vector
+ * \return This vector
+ */
+
+/**
+ * \fn Vector::operator*=(T scalar)
+ * \brief Multiply this vector by \a scalar element-wise
+ * \param[in] scalar The scalar
+ * \return This vector
+ */
+
+/**
+ * \fn Vector::operator/=(const Vector &other)
+ * \brief Divide this vector by \a other element-wise
+ * \param[in] other The other vector
+ * \return This vector
+ */
+
+/**
+ * \fn Vector::operator/=(T scalar)
+ * \brief Divide this vector by \a scalar element-wise
+ * \param[in] scalar The scalar
+ * \return This vector
+ */
+
+/**
+ * \fn Vector::min(const Vector &other) const
+ * \brief Calculate the minimum of this vector and \a other element-wise
+ * \param[in] other The other vector
+ * \return The element-wise minimum of this vector and \a other
+ */
+
+/**
+ * \fn Vector::min(T scalar) const
+ * \brief Calculate the minimum of this vector and \a scalar element-wise
+ * \param[in] scalar The scalar
+ * \return The element-wise minimum of this vector and \a scalar
+ */
+
+/**
+ * \fn Vector::max(const Vector &other) const
+ * \brief Calculate the maximum of this vector and \a other element-wise
+ * \param[in] other The other vector
+ * \return The element-wise maximum of this vector and \a other
+ */
+
+/**
+ * \fn Vector::max(T scalar) const
+ * \brief Calculate the maximum of this vector and \a scalar element-wise
+ * \param[in] scalar The scalar
+ * \return The element-wise maximum of this vector and \a scalar
+ */
+
+/**
+ * \fn Vector::dot(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 constexpr T &Vector::x()
+ * \brief Convenience function to access the first element of the vector
+ * \return The first element of the vector
+ */
+
+/**
+ * \fn constexpr T &Vector::y()
+ * \brief Convenience function to access the second element of the vector
+ * \return The second element of the vector
+ */
+
+/**
+ * \fn constexpr T &Vector::z()
+ * \brief Convenience function to access the third element of the vector
+ * \return The third element of the vector
+ */
+
+/**
+ * \fn constexpr const T &Vector::x() const
+ * \copydoc Vector::x()
+ */
+
+/**
+ * \fn constexpr const T &Vector::y() const
+ * \copydoc Vector::y()
+ */
+
+/**
+ * \fn constexpr const T &Vector::z() const
+ * \copydoc Vector::z()
+ */
+
+/**
+ * \fn constexpr T &Vector::r()
+ * \brief Convenience function to access the first element of the vector
+ * \return The first element of the vector
+ */
+
+/**
+ * \fn constexpr T &Vector::g()
+ * \brief Convenience function to access the second element of the vector
+ * \return The second element of the vector
+ */
+
+/**
+ * \fn constexpr T &Vector::b()
+ * \brief Convenience function to access the third element of the vector
+ * \return The third element of the vector
+ */
+
+/**
+ * \fn constexpr const T &Vector::r() const
+ * \copydoc Vector::r()
+ */
+
+/**
+ * \fn constexpr const T &Vector::g() const
+ * \copydoc Vector::g()
+ */
+
+/**
+ * \fn constexpr const T &Vector::b() const
+ * \copydoc Vector::b()
+ */
+
+/**
+ * \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::sum() const
+ * \brief Calculate the sum of all the vector elements
+ * \tparam R The type of the sum
+ *
+ * The type R of the sum defaults to the type T of the elements, but can be set
+ * explicitly to use a different type in case the type T would risk
+ * overflowing.
+ *
+ * \return The sum of all the vector elements
+ */
+
+/**
+ * \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
+ */
+
+/**
+ * \typedef RGB
+ * \brief A Vector of 3 elements representing an RGB pixel value
+ */
+
+/**
+ * \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/test/vector.cpp b/test/vector.cpp
new file mode 100644
index 000000000000..8e4ec77d7820
--- /dev/null
+++ b/test/vector.cpp
@@ -0,0 +1,100 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * Vector tests
+ */
+
+#include "../src/ipa/libipa/vector.h"
+
+#include <cmath>
+#include <iostream>
+
+#include "test.h"
+
+using namespace libcamera::ipa;
+
+#define ASSERT_EQ(a, b)							\
+if ((a) != (b)) {							\
+	std::cout << #a " != " #b << " (line " << __LINE__ << ")"	\
+		  << std::endl;						\
+	return TestFail;						\
+}
+
+class VectorTest : public Test
+{
+protected:
+	int run()
+	{
+		Vector<double, 3> v1{ 0.0 };
+
+		ASSERT_EQ(v1[0], 0.0);
+		ASSERT_EQ(v1[1], 0.0);
+		ASSERT_EQ(v1[2], 0.0);
+
+		ASSERT_EQ(v1.length(), 0.0);
+		ASSERT_EQ(v1.length2(), 0.0);
+
+		Vector<double, 3> v2{{ 1.0, 4.0, 8.0 }};
+
+		ASSERT_EQ(v2[0], 1.0);
+		ASSERT_EQ(v2[1], 4.0);
+		ASSERT_EQ(v2[2], 8.0);
+
+		ASSERT_EQ(v2.x(), 1.0);
+		ASSERT_EQ(v2.y(), 4.0);
+		ASSERT_EQ(v2.z(), 8.0);
+
+		ASSERT_EQ(v2.r(), 1.0);
+		ASSERT_EQ(v2.g(), 4.0);
+		ASSERT_EQ(v2.b(), 8.0);
+
+		ASSERT_EQ(v2.length2(), 81.0);
+		ASSERT_EQ(v2.length(), 9.0);
+		ASSERT_EQ(v2.sum(), 13.0);
+
+		Vector<double, 3> v3{ v2 };
+
+		ASSERT_EQ(v2, v3);
+
+		v3 = Vector<double, 3>{{ 4.0, 4.0, 4.0 }};
+
+		ASSERT_EQ(v2 + v3, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
+		ASSERT_EQ(v2 + 4.0, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
+		ASSERT_EQ(v2 - v3, (Vector<double, 3>{{ -3.0, 0.0, 4.0 }}));
+		ASSERT_EQ(v2 - 4.0, (Vector<double, 3>{{ -3.0, 0.0, 4.0 }}));
+		ASSERT_EQ(v2 * v3, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
+		ASSERT_EQ(v2 * 4.0, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
+		ASSERT_EQ(v2 / v3, (Vector<double, 3>{{ 0.25, 1.0, 2.0 }}));
+		ASSERT_EQ(v2 / 4.0, (Vector<double, 3>{{ 0.25, 1.0, 2.0 }}));
+
+		ASSERT_EQ(v2.min(v3), (Vector<double, 3>{{ 1.0, 4.0, 4.0 }}));
+		ASSERT_EQ(v2.min(4.0), (Vector<double, 3>{{ 1.0, 4.0, 4.0 }}));
+		ASSERT_EQ(v2.max(v3), (Vector<double, 3>{{ 4.0, 4.0, 8.0 }}));
+		ASSERT_EQ(v2.max(4.0), (Vector<double, 3>{{ 4.0, 4.0, 8.0 }}));
+
+		ASSERT_EQ(v2.dot(v3), 52.0);
+
+		v2 += v3;
+		ASSERT_EQ(v2, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
+		v2 -= v3;
+		ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+		v2 *= v3;
+		ASSERT_EQ(v2, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
+		v2 /= v3;
+		ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+
+		v2 += 4.0;
+		ASSERT_EQ(v2, (Vector<double, 3>{{ 5.0, 8.0, 12.0 }}));
+		v2 -= 4.0;
+		ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+		v2 *= 4.0;
+		ASSERT_EQ(v2, (Vector<double, 3>{{ 4.0, 16.0, 32.0 }}));
+		v2 /= 4.0;
+		ASSERT_EQ(v2, (Vector<double, 3>{{ 1.0, 4.0, 8.0 }}));
+
+		return TestPass;
+	}
+};
+
+TEST_REGISTER(VectorTest)