[v2,1/4] libcamera: geometry: Add floating-point version of Point class
diff mbox series

Message ID 20240426073612.1230283-2-paul.elder@ideasonboard.com
State New
Headers show
Series
  • ipa: Move Pwl from Raspberry Pi to libipa
Related show

Commit Message

Paul Elder April 26, 2024, 7:36 a.m. UTC
The piecewise linear function (Pwl) class from the Raspberry Pi IPA has
its own Point class while one already exists in libcamera's geometry.h
header. The reason is because libcamera's Point is on the plane of
integer, while Raspberry Pi's is on the plane of reals.

While making this a template class might be cleaner, it was deemed to be
too intrusive of a change, and it might not feel nice to need to specify
the type from a public API point of view. Hence we introduce a FPoint
class to designate a Point class on the plane of reals.

This is in preparation for copying/moving the Pwl class from the
Raspberry Pi IPA to libipa.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
---
Changes in v2:
- renamed FPoint to PointF
- add documentation
- add tests
---
 include/libcamera/geometry.h |  65 +++++++
 src/libcamera/geometry.cpp   | 123 +++++++++++-
 test/geometry.cpp            | 355 +++++++++++++++++++++++++++++++++++
 3 files changed, 542 insertions(+), 1 deletion(-)

Comments

Stefan Klug May 2, 2024, 7:18 p.m. UTC | #1
Hi Paul,

thanks for the patch.

On Fri, Apr 26, 2024 at 04:36:09PM +0900, Paul Elder wrote:
> The piecewise linear function (Pwl) class from the Raspberry Pi IPA has
> its own Point class while one already exists in libcamera's geometry.h
> header. The reason is because libcamera's Point is on the plane of
> integer, while Raspberry Pi's is on the plane of reals.

nit: s/integer/integers/

> 
> While making this a template class might be cleaner, it was deemed to be
> too intrusive of a change, and it might not feel nice to need to specify
> the type from a public API point of view. Hence we introduce a FPoint

nit: s/FPoint/PointF/

> class to designate a Point class on the plane of reals.
> 
> This is in preparation for copying/moving the Pwl class from the
> Raspberry Pi IPA to libipa.
> 
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> ---
> Changes in v2:
> - renamed FPoint to PointF
> - add documentation
> - add tests
> ---
>  include/libcamera/geometry.h |  65 +++++++
>  src/libcamera/geometry.cpp   | 123 +++++++++++-
>  test/geometry.cpp            | 355 +++++++++++++++++++++++++++++++++++
>  3 files changed, 542 insertions(+), 1 deletion(-)
> 
> diff --git a/include/libcamera/geometry.h b/include/libcamera/geometry.h
> index d7fdbe70..6aef1f39 100644
> --- a/include/libcamera/geometry.h
> +++ b/include/libcamera/geometry.h
> @@ -8,6 +8,7 @@
>  #pragma once
>  
>  #include <algorithm>
> +#include <cmath>
>  #include <ostream>
>  #include <string>
>  
> @@ -49,6 +50,70 @@ static inline bool operator!=(const Point &lhs, const Point &rhs)
>  
>  std::ostream &operator<<(std::ostream &out, const Point &p);
>  
> +struct PointF {
> +	constexpr PointF()
> +		: x(0), y(0)
> +	{
> +	}
> +
> +	constexpr PointF(double _x, double _y)
> +		: x(_x), y(_y)
> +	{
> +	}
> +
> +	constexpr PointF operator-() const
> +	{
> +		return PointF{ -x, -y };
> +	}
> +
> +	constexpr PointF operator-(const PointF &p) const
> +	{
> +		return PointF(x - p.x, y - p.y);
> +	}
> +
> +	constexpr PointF operator+(const PointF &p) const
> +	{
> +		return PointF(x + p.x, y + p.y);
> +	}
> +
> +	constexpr double operator%(const PointF &p) const
> +	{
> +		return x * p.x + y * p.y;
> +	}

This operator implementation is unexpected to me. Is that a common usage
for % ? I would need to look that up, whilst a dot() function would
convey the intend immediately (at least to me).

With or without that changed:
Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com> 

Cheers,
Stefan

> +
> +	constexpr PointF operator*(double f) const
> +	{
> +		return PointF(x * f, y * f);
> +	}
> +
> +	constexpr PointF operator/(double f) const
> +	{
> +		return PointF(x / f, y / f);
> +	}
> +
> +	constexpr double len2() const
> +	{
> +		return x * x + y * y;
> +	}
> +
> +	constexpr double len() const
> +	{
> +		return std::sqrt(len2());
> +	}
> +
> +	const std::string toString() const;
> +
> +	double x, y;
> +};
> +
> +bool operator==(const PointF &lhs, const PointF &rhs);
> +static inline bool operator!=(const PointF &lhs, const PointF &rhs)
> +{
> +	return !(lhs == rhs);
> +}
> +
> +std::ostream &operator<<(std::ostream &out, const PointF &p);
> +
>  class Size
>  {
>  public:
> diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp
> index 8d85b758..90b81282 100644
> --- a/src/libcamera/geometry.cpp
> +++ b/src/libcamera/geometry.cpp
> @@ -21,7 +21,7 @@ namespace libcamera {
>  
>  /**
>   * \class Point
> - * \brief Describe a point in two-dimensional space
> + * \brief Describe a point in two-dimensional integer space
>   *
>   * The Point structure defines a point in two-dimensional space with integer
>   * precision. The coordinates of a Point may be negative as well as positive.
> @@ -94,6 +94,127 @@ std::ostream &operator<<(std::ostream &out, const Point &p)
>  	return out;
>  }
>  
> +/**
> + * \class PointF
> + * \brief Describe a point in two-dimensional real space
> + *
> + * The Point structure defines a point in two-dimensional space with double
> + * precision. The coordinates of a Point may be negative as well as positive.
> + *
> + * This class exists separately from Point to not require all users of the
> + * Point class to have to use template parameters to specify a type.
> + */
> +
> +/**
> + * \fn PointF::PointF()
> + * \copydoc libcamera::Point::Point
> + */
> +
> +/**
> + * \fn PointF::PointF(double _x, double _y)
> + * \brief Construct a PointF at given \a _x and \a _y values
> + * \param[in] _x The x-coordinate
> + * \param[in] _y The y-coordinate
> + */
> +
> +/**
> + * \var PointF::x
> + * \copydoc libcamera::Point::x
> + */
> +
> +/**
> + * \var PointF::y
> + * \copydoc libcamera::Point::y
> + */
> +
> +/**
> + * \fn constexpr PointF PointF::operator-() const
> + * \copydoc libcamera::Point::operator-
> + */
> +
> +/**
> + * \fn constexpr PointF PointF::operator-(PointF const &p) const
> + * \brief Subtract one point from another, as if they were vectors
> + * \param[in] p The other point
> + * \return The difference of p from this point
> + */
> +
> +/**
> + * \fn PointF::operator+()
> + * \brief Add two points together, as if they were vectors
> + * \param[in] p The other point
> + * \return The sum of the two points
> + */
> +
> +/**
> + * \fn PointF::operator%()
> + * \brief Compute the dot product, treating the points as vectors
> + * \param[in] p The other point
> + * \return The dot product of the two points
> + */
> +
> +/**
> + * \fn PointF::operator*()
> + * \brief Scale up the point, as if it were a vector
> + * \param[in] f The factor
> + * \return The scaled point
> + */
> +
> +/**
> + * \fn PointF::operator/()
> + * \brief Scale down the point, as if it were a vector
> + * \param[in] f The factor
> + * \return The scaled point
> + */
> +
> +/**
> + * \fn PointF::len2()
> + * \brief Get the squared length of the point, as if it were a vector
> + * \return The squared length of the point
> + */
> +
> +/**
> + * \fn PointF::len()
> + * \brief Get the length of the point, as if it were a vector
> + * \return The length of the point
> + */
> +
> +/**
> + * \copydoc Point::toString()
> + */
> +const std::string PointF::toString() const
> +{
> +	std::stringstream ss;
> +	ss << *this;
> +
> +	return ss.str();
> +}
> +
> +/**
> + * \copydoc operator==(const Point &lhs, const Point &rhs)
> + */
> +bool operator==(const PointF &lhs, const PointF &rhs)
> +{
> +	return lhs.x == rhs.x && lhs.y == rhs.y;
> +}
> +
> +/**
> + * \fn bool operator!=(const Point &lhs, const Point &rhs)
> + * \copydoc libcamera::Point::operator!=
> + */
> +
> +/**
> + * \brief Insert a text representation of a PointF into an output stream
> + * \param[in] out The output stream
> + * \param[in] p The point
> + * \return The output stream \a out
> + */
> +std::ostream &operator<<(std::ostream &out, const PointF &p)
> +{
> +	out << "(" << p.x << ", " << p.y << ")";
> +	return out;
> +}
> +
>  /**
>   * \class Size
>   * \brief Describe a two-dimensional size
> diff --git a/test/geometry.cpp b/test/geometry.cpp
> index 008d51ea..7f1f5b1a 100644
> --- a/test/geometry.cpp
> +++ b/test/geometry.cpp
> @@ -33,6 +33,53 @@ protected:
>  		return true;
>  	}
>  
> +	template<typename T, typename U, typename V>
> +	bool compareF(const T &lhs, const U &rhs,
> +		      V (PointF::*op)(const U &) const,
> +		      const char *opName, V expect)
> +	{
> +		V result = (lhs.*op)(rhs);
> +
> +		if (result != expect) {
> +			cout << lhs << opName << " " << rhs
> +			     << "test failed" << std::endl;
> +			return false;
> +		}
> +
> +		return true;
> +	}
> +
> +	template<typename T, typename U, typename V>
> +	bool compareFScale(const T &lhs, const U &rhs,
> +			   V (PointF::*op)(U) const,
> +			   const char *opName, V expect)
> +	{
> +		V result = (lhs.*op)(rhs);
> +
> +		if (result != expect) {
> +			cout << lhs << opName << " " << rhs
> +			     << "test failed" << std::endl;
> +			return false;
> +		}
> +
> +		return true;
> +	}
> +
> +	template<typename T>
> +	bool compareFLen(const T &lhs, double (PointF::*op)() const,
> +			 const char *opName, double expect)
> +	{
> +		double result = (lhs.*op)();
> +
> +		if (result != expect) {
> +			cout << lhs << opName
> +			     << "test failed" << std::endl;
> +			return false;
> +		}
> +
> +		return true;
> +	}
> +
>  	int run()
>  	{
>  		/*
> @@ -88,6 +135,314 @@ protected:
>  			return TestFail;
>  		}
>  
> +		/*
> +		 * PointF tests
> +		 */
> +
> +		/* Equality */
> +		if (!compare(PointF(50.1, 100.1), PointF(50.1, 100.1), &operator==, "==", true))
> +			return TestFail;
> +
> +		if (!compare(PointF(-50.1, 100.1), PointF(-50.1, 100.1), &operator==, "==", true))
> +			return TestFail;
> +
> +		if (!compare(PointF(50.1, -100.1), PointF(50.1, -100.1), &operator==, "==", true))
> +			return TestFail;
> +
> +		if (!compare(PointF(-50.1, -100.1), PointF(-50.1, -100.1), &operator==, "==", true))
> +			return TestFail;
> +
> +		if (!compare(PointF(-50.1, -100.0), PointF(-50.1, -100), &operator==, "==", true))
> +			return TestFail;
> +
> +		/* Inequality */
> +		if (!compare(PointF(50.1, 100.1), PointF(50.1, 100.2), &operator!=, "!=", true))
> +			return TestFail;
> +
> +		if (!compare(PointF(-50.1, 100.1), PointF(-50.1, 100.01), &operator!=, "!=", true))
> +			return TestFail;
> +
> +		if (!compare(PointF(-50.1, 100.1), PointF(-50.1, 100.1), &operator!=, "!=", false))
> +			return TestFail;
> +
> +		if (!compare(PointF(50.1, -100.1), PointF(50.2, -100.1), &operator!=, "!=", true))
> +			return TestFail;
> +
> +		if (!compare(PointF(-50.1, -100.1), PointF(-50.01, -100.0), &operator!=, "!=", true))
> +			return TestFail;
> +
> +		if (!compare(PointF(-50.1, 100.1), PointF(50.1, 100.1), &operator!=, "!=", true))
> +			return TestFail;
> +
> +		if (!compare(PointF(50.1, -100.1), PointF(50.1, 100.1), &operator!=, "!=", true))
> +			return TestFail;
> +
> +		if (!compare(PointF(-50.1, -100.1), PointF(-50.1, -100.1), &operator!=, "!=", false))
> +			return TestFail;
> +
> +		/* Negation */
> +		if (PointF(50.1, 100.1) != -PointF(-50.1, -100.1) ||
> +		    PointF(50.1, 100.1) == -PointF(50.1, -100.1) ||
> +		    PointF(50.1, 100.1) == -PointF(-50.1, 100.1)) {
> +			cout << "PointF negation test failed" << endl;
> +			return TestFail;
> +		}
> +
> +		typedef PointF (PointF::*pointfOp)(const PointF &) const;
> +		typedef double (PointF::*pointfDotProd)(const PointF &) const;
> +		typedef PointF (PointF::*pointfScale)(double) const;
> +		typedef double (PointF::*pointfLen)() const;
> +
> +		/* Subtraction */
> +		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(50.1, 100.1),
> +			      static_cast<pointfOp>(&PointF::operator-), "-",
> +			      PointF(0, 0)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, 100.1), PointF(-50.1, 100.1),
> +			      static_cast<pointfOp>(&PointF::operator-), "-",
> +			      PointF(0, 0)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(50.1, -100.1), PointF(50.1, -100.1),
> +			      static_cast<pointfOp>(&PointF::operator-), "-",
> +			      PointF(0, 0)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, -100.1), PointF(-50.1, -100.1),
> +			      static_cast<pointfOp>(&PointF::operator-), "-",
> +			      PointF(0, 0)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(-50.1, 100.1),
> +			      static_cast<pointfOp>(&PointF::operator-), "-",
> +			      PointF(100.2, 0)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(50.1, -100.1),
> +			      static_cast<pointfOp>(&PointF::operator-), "-",
> +			      PointF(0, 200.2)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(-50.1, -100.1),
> +			      static_cast<pointfOp>(&PointF::operator-), "-",
> +			      PointF(100.2, 200.2)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, 100.1), PointF(50.1, 100.1),
> +			      static_cast<pointfOp>(&PointF::operator-), "-",
> +			      PointF(-100.2, 0)))
> +			return TestFail;
> +
> +		/* Addition */
> +		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(50.1, 100.1),
> +			      static_cast<pointfOp>(&PointF::operator+), "+",
> +			      PointF(100.2, 200.2)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, 100.1), PointF(-50.1, 100.1),
> +			      static_cast<pointfOp>(&PointF::operator+), "+",
> +			      PointF(-100.2, 200.2)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(50.1, -100.1), PointF(50.1, -100.1),
> +			      static_cast<pointfOp>(&PointF::operator+), "+",
> +			      PointF(100.2, -200.2)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, -100), PointF(-50.1, -100.1),
> +			      static_cast<pointfOp>(&PointF::operator+), "+",
> +			      PointF(-100.2, -200.1)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(-50.1, 100.1),
> +			      static_cast<pointfOp>(&PointF::operator+), "+",
> +			      PointF(0, 200.2)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.0), PointF(50.1, -100.1),
> +			      static_cast<pointfOp>(&PointF::operator+), "+",
> +			      PointF(100.2, -0.09999999999999432)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(-50.1, -100.1),
> +			      static_cast<pointfOp>(&PointF::operator+), "+",
> +			      PointF(0, 0)))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, 100.1), PointF(50.1, 100.1),
> +			      static_cast<pointfOp>(&PointF::operator+), "+",
> +			      PointF(0, 200.2)))
> +			return TestFail;
> +
> +		/* Dot product */
> +		if (!compareF<PointF, PointF, double>(PointF(50.1, 100.1), PointF(50.1, 100.1),
> +			      static_cast<pointfDotProd>(&PointF::operator%), "%",
> +			      12530.019999999999))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, double>(PointF(50.1, 100.1), PointF(50.1, -100.1),
> +			      static_cast<pointfDotProd>(&PointF::operator%), "%",
> +			      -7509.999999999998))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, double>(PointF(50.1, 100.1), PointF(-50.1, 100.1),
> +			      static_cast<pointfDotProd>(&PointF::operator%), "%",
> +			      7509.999999999998))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, double>(PointF(50.1, 100.1), PointF(-50.1, -100.1),
> +			      static_cast<pointfDotProd>(&PointF::operator%), "%",
> +			      -12530.019999999999))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, double>(PointF(-50.1, 100.1), PointF(-50.1, 100.1),
> +			      static_cast<pointfDotProd>(&PointF::operator%), "%",
> +			      12530.019999999999))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, double>(PointF(50.1, -100.1), PointF(50.1, -100.1),
> +			      static_cast<pointfDotProd>(&PointF::operator%), "%",
> +			      12530.019999999999))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, double>(PointF(-50.1, -100.1), PointF(-50.1, -100.1),
> +			      static_cast<pointfDotProd>(&PointF::operator%), "%",
> +			      12530.019999999999))
> +			return TestFail;
> +
> +		if (!compareF<PointF, PointF, double>(PointF(0, 0), PointF(0, 0),
> +			      static_cast<pointfDotProd>(&PointF::operator%), "%",
> +			      0))
> +			return TestFail;
> +
> +
> +		/* Scaling up */
> +		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 0,
> +				   static_cast<pointfScale>(&PointF::operator*), "*",
> +				   PointF(0, 0)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 1,
> +				   static_cast<pointfScale>(&PointF::operator*), "*",
> +				   PointF(10.5, -100.1)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 1.0,
> +				   static_cast<pointfScale>(&PointF::operator*), "*",
> +				   PointF(10.5, -100.1)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), -4.2,
> +				   static_cast<pointfScale>(&PointF::operator*), "*",
> +				   PointF(-44.1, 420.42)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 4.2,
> +				   static_cast<pointfScale>(&PointF::operator*), "*",
> +				   PointF(44.1, -420.42)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(0, -100.1), 4.2,
> +				   static_cast<pointfScale>(&PointF::operator*), "*",
> +				   PointF(0, -420.42)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(-50.1, -100.1), 4.2,
> +				   static_cast<pointfScale>(&PointF::operator*), "*",
> +				   PointF(-210.42000000000002, -420.42)))
> +			return TestFail;
> +
> +		/* Scaling down */
> +		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 1,
> +				   static_cast<pointfScale>(&PointF::operator/), "/",
> +				   PointF(10.5, -100.1)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 1.0,
> +				   static_cast<pointfScale>(&PointF::operator/), "/",
> +				   PointF(10.5, -100.1)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), -4.2,
> +				   static_cast<pointfScale>(&PointF::operator/), "/",
> +				   PointF(-2.5, 23.833333333333332)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 4.2,
> +				   static_cast<pointfScale>(&PointF::operator/), "/",
> +				   PointF(2.5, -23.833333333333332)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(0, -100.1), 4.2,
> +				   static_cast<pointfScale>(&PointF::operator/), "/",
> +				   PointF(0, -23.833333333333332)))
> +			return TestFail;
> +
> +		if (!compareFScale<PointF, double, PointF>(PointF(-50.1, -100.1), 4.2,
> +				   static_cast<pointfScale>(&PointF::operator/), "/",
> +				   PointF(-11.928571428571429, -23.833333333333332)))
> +			return TestFail;
> +
> +
> +		/* Squared length */
> +		if (!compareFLen<PointF>(PointF(0, 0),
> +					 static_cast<pointfLen>(&PointF::len2), "len2",
> +					 0))
> +			return TestFail;
> +
> +		if (!compareFLen<PointF>(PointF(10.4, 0),
> +					 static_cast<pointfLen>(&PointF::len2), "len2",
> +					 108.16000000000001))
> +			return TestFail;
> +
> +		if (!compareFLen<PointF>(PointF(10.4, 50.1),
> +					 static_cast<pointfLen>(&PointF::len2), "len2",
> +					 2618.17))
> +			return TestFail;
> +
> +		if (!compareFLen<PointF>(PointF(-10.4, 50.1),
> +					 static_cast<pointfLen>(&PointF::len2), "len2",
> +					 2618.17))
> +			return TestFail;
> +
> +		if (!compareFLen<PointF>(PointF(-10.4, -50.1),
> +					 static_cast<pointfLen>(&PointF::len2), "len2",
> +					 2618.17))
> +			return TestFail;
> +
> +		/* Length */
> +		if (!compareFLen<PointF>(PointF(0, 0),
> +					 static_cast<pointfLen>(&PointF::len), "len",
> +					 0))
> +			return TestFail;
> +
> +		if (!compareFLen<PointF>(PointF(10.4, 0),
> +					 static_cast<pointfLen>(&PointF::len), "len",
> +					 10.4))
> +			return TestFail;
> +
> +		if (!compareFLen<PointF>(PointF(10.4, 50.1),
> +					 static_cast<pointfLen>(&PointF::len), "len",
> +					 51.16805644149483))
> +			return TestFail;
> +
> +		if (!compareFLen<PointF>(PointF(-10.4, 50.1),
> +					 static_cast<pointfLen>(&PointF::len), "len",
> +					 51.16805644149483))
> +			return TestFail;
> +
> +		if (!compareFLen<PointF>(PointF(-10.4, -50.1),
> +					 static_cast<pointfLen>(&PointF::len), "len",
> +					 51.16805644149483))
> +			return TestFail;
> +
> +		/* Default constructor */
> +		if (PointF() != PointF(0, 0)) {
> +			cout << "Default constructor test failed" << endl;
> +			return TestFail;
> +		}
> +
>  		/*
>  		 * Size tests
>  		 */
> -- 
> 2.39.2
>

Patch
diff mbox series

diff --git a/include/libcamera/geometry.h b/include/libcamera/geometry.h
index d7fdbe70..6aef1f39 100644
--- a/include/libcamera/geometry.h
+++ b/include/libcamera/geometry.h
@@ -8,6 +8,7 @@ 
 #pragma once
 
 #include <algorithm>
+#include <cmath>
 #include <ostream>
 #include <string>
 
@@ -49,6 +50,70 @@  static inline bool operator!=(const Point &lhs, const Point &rhs)
 
 std::ostream &operator<<(std::ostream &out, const Point &p);
 
+struct PointF {
+	constexpr PointF()
+		: x(0), y(0)
+	{
+	}
+
+	constexpr PointF(double _x, double _y)
+		: x(_x), y(_y)
+	{
+	}
+
+	constexpr PointF operator-() const
+	{
+		return PointF{ -x, -y };
+	}
+
+	constexpr PointF operator-(const PointF &p) const
+	{
+		return PointF(x - p.x, y - p.y);
+	}
+
+	constexpr PointF operator+(const PointF &p) const
+	{
+		return PointF(x + p.x, y + p.y);
+	}
+
+	constexpr double operator%(const PointF &p) const
+	{
+		return x * p.x + y * p.y;
+	}
+
+	constexpr PointF operator*(double f) const
+	{
+		return PointF(x * f, y * f);
+	}
+
+	constexpr PointF operator/(double f) const
+	{
+		return PointF(x / f, y / f);
+	}
+
+	constexpr double len2() const
+	{
+		return x * x + y * y;
+	}
+
+	constexpr double len() const
+	{
+		return std::sqrt(len2());
+	}
+
+	const std::string toString() const;
+
+	double x, y;
+};
+
+bool operator==(const PointF &lhs, const PointF &rhs);
+static inline bool operator!=(const PointF &lhs, const PointF &rhs)
+{
+	return !(lhs == rhs);
+}
+
+std::ostream &operator<<(std::ostream &out, const PointF &p);
+
 class Size
 {
 public:
diff --git a/src/libcamera/geometry.cpp b/src/libcamera/geometry.cpp
index 8d85b758..90b81282 100644
--- a/src/libcamera/geometry.cpp
+++ b/src/libcamera/geometry.cpp
@@ -21,7 +21,7 @@  namespace libcamera {
 
 /**
  * \class Point
- * \brief Describe a point in two-dimensional space
+ * \brief Describe a point in two-dimensional integer space
  *
  * The Point structure defines a point in two-dimensional space with integer
  * precision. The coordinates of a Point may be negative as well as positive.
@@ -94,6 +94,127 @@  std::ostream &operator<<(std::ostream &out, const Point &p)
 	return out;
 }
 
+/**
+ * \class PointF
+ * \brief Describe a point in two-dimensional real space
+ *
+ * The Point structure defines a point in two-dimensional space with double
+ * precision. The coordinates of a Point may be negative as well as positive.
+ *
+ * This class exists separately from Point to not require all users of the
+ * Point class to have to use template parameters to specify a type.
+ */
+
+/**
+ * \fn PointF::PointF()
+ * \copydoc libcamera::Point::Point
+ */
+
+/**
+ * \fn PointF::PointF(double _x, double _y)
+ * \brief Construct a PointF at given \a _x and \a _y values
+ * \param[in] _x The x-coordinate
+ * \param[in] _y The y-coordinate
+ */
+
+/**
+ * \var PointF::x
+ * \copydoc libcamera::Point::x
+ */
+
+/**
+ * \var PointF::y
+ * \copydoc libcamera::Point::y
+ */
+
+/**
+ * \fn constexpr PointF PointF::operator-() const
+ * \copydoc libcamera::Point::operator-
+ */
+
+/**
+ * \fn constexpr PointF PointF::operator-(PointF const &p) const
+ * \brief Subtract one point from another, as if they were vectors
+ * \param[in] p The other point
+ * \return The difference of p from this point
+ */
+
+/**
+ * \fn PointF::operator+()
+ * \brief Add two points together, as if they were vectors
+ * \param[in] p The other point
+ * \return The sum of the two points
+ */
+
+/**
+ * \fn PointF::operator%()
+ * \brief Compute the dot product, treating the points as vectors
+ * \param[in] p The other point
+ * \return The dot product of the two points
+ */
+
+/**
+ * \fn PointF::operator*()
+ * \brief Scale up the point, as if it were a vector
+ * \param[in] f The factor
+ * \return The scaled point
+ */
+
+/**
+ * \fn PointF::operator/()
+ * \brief Scale down the point, as if it were a vector
+ * \param[in] f The factor
+ * \return The scaled point
+ */
+
+/**
+ * \fn PointF::len2()
+ * \brief Get the squared length of the point, as if it were a vector
+ * \return The squared length of the point
+ */
+
+/**
+ * \fn PointF::len()
+ * \brief Get the length of the point, as if it were a vector
+ * \return The length of the point
+ */
+
+/**
+ * \copydoc Point::toString()
+ */
+const std::string PointF::toString() const
+{
+	std::stringstream ss;
+	ss << *this;
+
+	return ss.str();
+}
+
+/**
+ * \copydoc operator==(const Point &lhs, const Point &rhs)
+ */
+bool operator==(const PointF &lhs, const PointF &rhs)
+{
+	return lhs.x == rhs.x && lhs.y == rhs.y;
+}
+
+/**
+ * \fn bool operator!=(const Point &lhs, const Point &rhs)
+ * \copydoc libcamera::Point::operator!=
+ */
+
+/**
+ * \brief Insert a text representation of a PointF into an output stream
+ * \param[in] out The output stream
+ * \param[in] p The point
+ * \return The output stream \a out
+ */
+std::ostream &operator<<(std::ostream &out, const PointF &p)
+{
+	out << "(" << p.x << ", " << p.y << ")";
+	return out;
+}
+
 /**
  * \class Size
  * \brief Describe a two-dimensional size
diff --git a/test/geometry.cpp b/test/geometry.cpp
index 008d51ea..7f1f5b1a 100644
--- a/test/geometry.cpp
+++ b/test/geometry.cpp
@@ -33,6 +33,53 @@  protected:
 		return true;
 	}
 
+	template<typename T, typename U, typename V>
+	bool compareF(const T &lhs, const U &rhs,
+		      V (PointF::*op)(const U &) const,
+		      const char *opName, V expect)
+	{
+		V result = (lhs.*op)(rhs);
+
+		if (result != expect) {
+			cout << lhs << opName << " " << rhs
+			     << "test failed" << std::endl;
+			return false;
+		}
+
+		return true;
+	}
+
+	template<typename T, typename U, typename V>
+	bool compareFScale(const T &lhs, const U &rhs,
+			   V (PointF::*op)(U) const,
+			   const char *opName, V expect)
+	{
+		V result = (lhs.*op)(rhs);
+
+		if (result != expect) {
+			cout << lhs << opName << " " << rhs
+			     << "test failed" << std::endl;
+			return false;
+		}
+
+		return true;
+	}
+
+	template<typename T>
+	bool compareFLen(const T &lhs, double (PointF::*op)() const,
+			 const char *opName, double expect)
+	{
+		double result = (lhs.*op)();
+
+		if (result != expect) {
+			cout << lhs << opName
+			     << "test failed" << std::endl;
+			return false;
+		}
+
+		return true;
+	}
+
 	int run()
 	{
 		/*
@@ -88,6 +135,314 @@  protected:
 			return TestFail;
 		}
 
+		/*
+		 * PointF tests
+		 */
+
+		/* Equality */
+		if (!compare(PointF(50.1, 100.1), PointF(50.1, 100.1), &operator==, "==", true))
+			return TestFail;
+
+		if (!compare(PointF(-50.1, 100.1), PointF(-50.1, 100.1), &operator==, "==", true))
+			return TestFail;
+
+		if (!compare(PointF(50.1, -100.1), PointF(50.1, -100.1), &operator==, "==", true))
+			return TestFail;
+
+		if (!compare(PointF(-50.1, -100.1), PointF(-50.1, -100.1), &operator==, "==", true))
+			return TestFail;
+
+		if (!compare(PointF(-50.1, -100.0), PointF(-50.1, -100), &operator==, "==", true))
+			return TestFail;
+
+		/* Inequality */
+		if (!compare(PointF(50.1, 100.1), PointF(50.1, 100.2), &operator!=, "!=", true))
+			return TestFail;
+
+		if (!compare(PointF(-50.1, 100.1), PointF(-50.1, 100.01), &operator!=, "!=", true))
+			return TestFail;
+
+		if (!compare(PointF(-50.1, 100.1), PointF(-50.1, 100.1), &operator!=, "!=", false))
+			return TestFail;
+
+		if (!compare(PointF(50.1, -100.1), PointF(50.2, -100.1), &operator!=, "!=", true))
+			return TestFail;
+
+		if (!compare(PointF(-50.1, -100.1), PointF(-50.01, -100.0), &operator!=, "!=", true))
+			return TestFail;
+
+		if (!compare(PointF(-50.1, 100.1), PointF(50.1, 100.1), &operator!=, "!=", true))
+			return TestFail;
+
+		if (!compare(PointF(50.1, -100.1), PointF(50.1, 100.1), &operator!=, "!=", true))
+			return TestFail;
+
+		if (!compare(PointF(-50.1, -100.1), PointF(-50.1, -100.1), &operator!=, "!=", false))
+			return TestFail;
+
+		/* Negation */
+		if (PointF(50.1, 100.1) != -PointF(-50.1, -100.1) ||
+		    PointF(50.1, 100.1) == -PointF(50.1, -100.1) ||
+		    PointF(50.1, 100.1) == -PointF(-50.1, 100.1)) {
+			cout << "PointF negation test failed" << endl;
+			return TestFail;
+		}
+
+		typedef PointF (PointF::*pointfOp)(const PointF &) const;
+		typedef double (PointF::*pointfDotProd)(const PointF &) const;
+		typedef PointF (PointF::*pointfScale)(double) const;
+		typedef double (PointF::*pointfLen)() const;
+
+		/* Subtraction */
+		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(50.1, 100.1),
+			      static_cast<pointfOp>(&PointF::operator-), "-",
+			      PointF(0, 0)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, 100.1), PointF(-50.1, 100.1),
+			      static_cast<pointfOp>(&PointF::operator-), "-",
+			      PointF(0, 0)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(50.1, -100.1), PointF(50.1, -100.1),
+			      static_cast<pointfOp>(&PointF::operator-), "-",
+			      PointF(0, 0)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, -100.1), PointF(-50.1, -100.1),
+			      static_cast<pointfOp>(&PointF::operator-), "-",
+			      PointF(0, 0)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(-50.1, 100.1),
+			      static_cast<pointfOp>(&PointF::operator-), "-",
+			      PointF(100.2, 0)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(50.1, -100.1),
+			      static_cast<pointfOp>(&PointF::operator-), "-",
+			      PointF(0, 200.2)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(-50.1, -100.1),
+			      static_cast<pointfOp>(&PointF::operator-), "-",
+			      PointF(100.2, 200.2)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, 100.1), PointF(50.1, 100.1),
+			      static_cast<pointfOp>(&PointF::operator-), "-",
+			      PointF(-100.2, 0)))
+			return TestFail;
+
+		/* Addition */
+		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(50.1, 100.1),
+			      static_cast<pointfOp>(&PointF::operator+), "+",
+			      PointF(100.2, 200.2)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, 100.1), PointF(-50.1, 100.1),
+			      static_cast<pointfOp>(&PointF::operator+), "+",
+			      PointF(-100.2, 200.2)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(50.1, -100.1), PointF(50.1, -100.1),
+			      static_cast<pointfOp>(&PointF::operator+), "+",
+			      PointF(100.2, -200.2)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, -100), PointF(-50.1, -100.1),
+			      static_cast<pointfOp>(&PointF::operator+), "+",
+			      PointF(-100.2, -200.1)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(-50.1, 100.1),
+			      static_cast<pointfOp>(&PointF::operator+), "+",
+			      PointF(0, 200.2)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.0), PointF(50.1, -100.1),
+			      static_cast<pointfOp>(&PointF::operator+), "+",
+			      PointF(100.2, -0.09999999999999432)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(50.1, 100.1), PointF(-50.1, -100.1),
+			      static_cast<pointfOp>(&PointF::operator+), "+",
+			      PointF(0, 0)))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, PointF>(PointF(-50.1, 100.1), PointF(50.1, 100.1),
+			      static_cast<pointfOp>(&PointF::operator+), "+",
+			      PointF(0, 200.2)))
+			return TestFail;
+
+		/* Dot product */
+		if (!compareF<PointF, PointF, double>(PointF(50.1, 100.1), PointF(50.1, 100.1),
+			      static_cast<pointfDotProd>(&PointF::operator%), "%",
+			      12530.019999999999))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, double>(PointF(50.1, 100.1), PointF(50.1, -100.1),
+			      static_cast<pointfDotProd>(&PointF::operator%), "%",
+			      -7509.999999999998))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, double>(PointF(50.1, 100.1), PointF(-50.1, 100.1),
+			      static_cast<pointfDotProd>(&PointF::operator%), "%",
+			      7509.999999999998))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, double>(PointF(50.1, 100.1), PointF(-50.1, -100.1),
+			      static_cast<pointfDotProd>(&PointF::operator%), "%",
+			      -12530.019999999999))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, double>(PointF(-50.1, 100.1), PointF(-50.1, 100.1),
+			      static_cast<pointfDotProd>(&PointF::operator%), "%",
+			      12530.019999999999))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, double>(PointF(50.1, -100.1), PointF(50.1, -100.1),
+			      static_cast<pointfDotProd>(&PointF::operator%), "%",
+			      12530.019999999999))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, double>(PointF(-50.1, -100.1), PointF(-50.1, -100.1),
+			      static_cast<pointfDotProd>(&PointF::operator%), "%",
+			      12530.019999999999))
+			return TestFail;
+
+		if (!compareF<PointF, PointF, double>(PointF(0, 0), PointF(0, 0),
+			      static_cast<pointfDotProd>(&PointF::operator%), "%",
+			      0))
+			return TestFail;
+
+
+		/* Scaling up */
+		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 0,
+				   static_cast<pointfScale>(&PointF::operator*), "*",
+				   PointF(0, 0)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 1,
+				   static_cast<pointfScale>(&PointF::operator*), "*",
+				   PointF(10.5, -100.1)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 1.0,
+				   static_cast<pointfScale>(&PointF::operator*), "*",
+				   PointF(10.5, -100.1)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), -4.2,
+				   static_cast<pointfScale>(&PointF::operator*), "*",
+				   PointF(-44.1, 420.42)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 4.2,
+				   static_cast<pointfScale>(&PointF::operator*), "*",
+				   PointF(44.1, -420.42)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(0, -100.1), 4.2,
+				   static_cast<pointfScale>(&PointF::operator*), "*",
+				   PointF(0, -420.42)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(-50.1, -100.1), 4.2,
+				   static_cast<pointfScale>(&PointF::operator*), "*",
+				   PointF(-210.42000000000002, -420.42)))
+			return TestFail;
+
+		/* Scaling down */
+		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 1,
+				   static_cast<pointfScale>(&PointF::operator/), "/",
+				   PointF(10.5, -100.1)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 1.0,
+				   static_cast<pointfScale>(&PointF::operator/), "/",
+				   PointF(10.5, -100.1)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), -4.2,
+				   static_cast<pointfScale>(&PointF::operator/), "/",
+				   PointF(-2.5, 23.833333333333332)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(10.5, -100.1), 4.2,
+				   static_cast<pointfScale>(&PointF::operator/), "/",
+				   PointF(2.5, -23.833333333333332)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(0, -100.1), 4.2,
+				   static_cast<pointfScale>(&PointF::operator/), "/",
+				   PointF(0, -23.833333333333332)))
+			return TestFail;
+
+		if (!compareFScale<PointF, double, PointF>(PointF(-50.1, -100.1), 4.2,
+				   static_cast<pointfScale>(&PointF::operator/), "/",
+				   PointF(-11.928571428571429, -23.833333333333332)))
+			return TestFail;
+
+
+		/* Squared length */
+		if (!compareFLen<PointF>(PointF(0, 0),
+					 static_cast<pointfLen>(&PointF::len2), "len2",
+					 0))
+			return TestFail;
+
+		if (!compareFLen<PointF>(PointF(10.4, 0),
+					 static_cast<pointfLen>(&PointF::len2), "len2",
+					 108.16000000000001))
+			return TestFail;
+
+		if (!compareFLen<PointF>(PointF(10.4, 50.1),
+					 static_cast<pointfLen>(&PointF::len2), "len2",
+					 2618.17))
+			return TestFail;
+
+		if (!compareFLen<PointF>(PointF(-10.4, 50.1),
+					 static_cast<pointfLen>(&PointF::len2), "len2",
+					 2618.17))
+			return TestFail;
+
+		if (!compareFLen<PointF>(PointF(-10.4, -50.1),
+					 static_cast<pointfLen>(&PointF::len2), "len2",
+					 2618.17))
+			return TestFail;
+
+		/* Length */
+		if (!compareFLen<PointF>(PointF(0, 0),
+					 static_cast<pointfLen>(&PointF::len), "len",
+					 0))
+			return TestFail;
+
+		if (!compareFLen<PointF>(PointF(10.4, 0),
+					 static_cast<pointfLen>(&PointF::len), "len",
+					 10.4))
+			return TestFail;
+
+		if (!compareFLen<PointF>(PointF(10.4, 50.1),
+					 static_cast<pointfLen>(&PointF::len), "len",
+					 51.16805644149483))
+			return TestFail;
+
+		if (!compareFLen<PointF>(PointF(-10.4, 50.1),
+					 static_cast<pointfLen>(&PointF::len), "len",
+					 51.16805644149483))
+			return TestFail;
+
+		if (!compareFLen<PointF>(PointF(-10.4, -50.1),
+					 static_cast<pointfLen>(&PointF::len), "len",
+					 51.16805644149483))
+			return TestFail;
+
+		/* Default constructor */
+		if (PointF() != PointF(0, 0)) {
+			cout << "Default constructor test failed" << endl;
+			return TestFail;
+		}
+
 		/*
 		 * Size tests
 		 */