[{"id":29227,"web_url":"https://patchwork.libcamera.org/comment/29227/","msgid":"<20240415125231.rpwuwobce7ws7cqa@jasper>","date":"2024-04-15T12:52:31","subject":"Re: [PATCH 4/4] ipa: rpi: controller: Use libipa's Pwl class","submitter":{"id":184,"url":"https://patchwork.libcamera.org/api/people/184/","name":"Stefan Klug","email":"stefan.klug@ideasonboard.com"},"content":"Hi Paul,\n\nthanks for the patch.\n\nOn Fri, Apr 05, 2024 at 05:02:59PM +0900, Paul Elder wrote:\n> To reduce code duplication, use the Pwl class from libipa. This also\n> removes the Pwl class from the Raspberry Pi IPA.\n> \n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n\nLooks good to me.\nReviewed-by: Stefan Klug <stefan.klug@ideasonboard.com> \n\nCheers,\nStefan\n\n> ---\n>  src/ipa/rpi/controller/cac_status.h        |   2 -\n>  src/ipa/rpi/controller/contrast_status.h   |   4 +-\n>  src/ipa/rpi/controller/meson.build         |   2 +-\n>  src/ipa/rpi/controller/pwl.cpp             | 269 ---------------------\n>  src/ipa/rpi/controller/pwl.h               | 127 ----------\n>  src/ipa/rpi/controller/rpi/af.cpp          |   4 +-\n>  src/ipa/rpi/controller/rpi/af.h            |   5 +-\n>  src/ipa/rpi/controller/rpi/agc_channel.cpp |   8 +-\n>  src/ipa/rpi/controller/rpi/agc_channel.h   |   7 +-\n>  src/ipa/rpi/controller/rpi/awb.cpp         |  40 +--\n>  src/ipa/rpi/controller/rpi/awb.h           |  23 +-\n>  src/ipa/rpi/controller/rpi/ccm.cpp         |   4 +-\n>  src/ipa/rpi/controller/rpi/ccm.h           |   5 +-\n>  src/ipa/rpi/controller/rpi/contrast.cpp    |  14 +-\n>  src/ipa/rpi/controller/rpi/contrast.h      |   5 +-\n>  src/ipa/rpi/controller/rpi/geq.cpp         |   5 +-\n>  src/ipa/rpi/controller/rpi/geq.h           |   4 +-\n>  src/ipa/rpi/controller/rpi/hdr.cpp         |   6 +-\n>  src/ipa/rpi/controller/rpi/hdr.h           |   9 +-\n>  src/ipa/rpi/controller/rpi/tonemap.cpp     |   2 +-\n>  src/ipa/rpi/controller/rpi/tonemap.h       |   5 +-\n>  src/ipa/rpi/controller/tonemap_status.h    |   4 +-\n>  22 files changed, 83 insertions(+), 471 deletions(-)\n>  delete mode 100644 src/ipa/rpi/controller/pwl.cpp\n>  delete mode 100644 src/ipa/rpi/controller/pwl.h\n> \n> diff --git a/src/ipa/rpi/controller/cac_status.h b/src/ipa/rpi/controller/cac_status.h\n> index 475d4c5c..adffce41 100644\n> --- a/src/ipa/rpi/controller/cac_status.h\n> +++ b/src/ipa/rpi/controller/cac_status.h\n> @@ -6,8 +6,6 @@\n>   */\n>  #pragma once\n>  \n> -#include \"pwl.h\"\n> -\n>  struct CacStatus {\n>  \tstd::vector<double> lutRx;\n>  \tstd::vector<double> lutRy;\n> diff --git a/src/ipa/rpi/controller/contrast_status.h b/src/ipa/rpi/controller/contrast_status.h\n> index fb9fe4ba..c9fbc3f6 100644\n> --- a/src/ipa/rpi/controller/contrast_status.h\n> +++ b/src/ipa/rpi/controller/contrast_status.h\n> @@ -6,7 +6,7 @@\n>   */\n>  #pragma once\n>  \n> -#include \"pwl.h\"\n> +#include \"libipa/pwl.h\"\n>  \n>  /*\n>   * The \"contrast\" algorithm creates a gamma curve, optionally doing a little bit\n> @@ -14,7 +14,7 @@\n>   */\n>  \n>  struct ContrastStatus {\n> -\tRPiController::Pwl gammaCurve;\n> +\tlibcamera::ipa::Pwl gammaCurve;\n>  \tdouble brightness;\n>  \tdouble contrast;\n>  };\n> diff --git a/src/ipa/rpi/controller/meson.build b/src/ipa/rpi/controller/meson.build\n> index 32a4d31c..74b74888 100644\n> --- a/src/ipa/rpi/controller/meson.build\n> +++ b/src/ipa/rpi/controller/meson.build\n> @@ -5,7 +5,6 @@ rpi_ipa_controller_sources = files([\n>      'controller.cpp',\n>      'device_status.cpp',\n>      'histogram.cpp',\n> -    'pwl.cpp',\n>      'rpi/af.cpp',\n>      'rpi/agc.cpp',\n>      'rpi/agc_channel.cpp',\n> @@ -32,4 +31,5 @@ rpi_ipa_controller_deps = [\n>  ]\n>  \n>  rpi_ipa_controller_lib = static_library('rpi_ipa_controller', rpi_ipa_controller_sources,\n> +                                        include_directories : libipa_includes,\n>                                          dependencies : rpi_ipa_controller_deps)\n> diff --git a/src/ipa/rpi/controller/pwl.cpp b/src/ipa/rpi/controller/pwl.cpp\n> deleted file mode 100644\n> index 70c2e24b..00000000\n> --- a/src/ipa/rpi/controller/pwl.cpp\n> +++ /dev/null\n> @@ -1,269 +0,0 @@\n> -/* SPDX-License-Identifier: BSD-2-Clause */\n> -/*\n> - * Copyright (C) 2019, Raspberry Pi Ltd\n> - *\n> - * pwl.cpp - piecewise linear functions\n> - */\n> -\n> -#include <cassert>\n> -#include <cmath>\n> -#include <stdexcept>\n> -\n> -#include \"pwl.h\"\n> -\n> -using namespace RPiController;\n> -\n> -int Pwl::read(const libcamera::YamlObject &params)\n> -{\n> -\tif (!params.size() || params.size() % 2)\n> -\t\treturn -EINVAL;\n> -\n> -\tconst auto &list = params.asList();\n> -\n> -\tfor (auto it = list.begin(); it != list.end(); it++) {\n> -\t\tauto x = it->get<double>();\n> -\t\tif (!x)\n> -\t\t\treturn -EINVAL;\n> -\t\tif (it != list.begin() && *x <= points_.back().x)\n> -\t\t\treturn -EINVAL;\n> -\n> -\t\tauto y = (++it)->get<double>();\n> -\t\tif (!y)\n> -\t\t\treturn -EINVAL;\n> -\n> -\t\tpoints_.push_back(Point(*x, *y));\n> -\t}\n> -\n> -\treturn 0;\n> -}\n> -\n> -void Pwl::append(double x, double y, const double eps)\n> -{\n> -\tif (points_.empty() || points_.back().x + eps < x)\n> -\t\tpoints_.push_back(Point(x, y));\n> -}\n> -\n> -void Pwl::prepend(double x, double y, const double eps)\n> -{\n> -\tif (points_.empty() || points_.front().x - eps > x)\n> -\t\tpoints_.insert(points_.begin(), Point(x, y));\n> -}\n> -\n> -Pwl::Interval Pwl::domain() const\n> -{\n> -\treturn Interval(points_[0].x, points_[points_.size() - 1].x);\n> -}\n> -\n> -Pwl::Interval Pwl::range() const\n> -{\n> -\tdouble lo = points_[0].y, hi = lo;\n> -\tfor (auto &p : points_)\n> -\t\tlo = std::min(lo, p.y), hi = std::max(hi, p.y);\n> -\treturn Interval(lo, hi);\n> -}\n> -\n> -bool Pwl::empty() const\n> -{\n> -\treturn points_.empty();\n> -}\n> -\n> -double Pwl::eval(double x, int *spanPtr, bool updateSpan) const\n> -{\n> -\tint span = findSpan(x, spanPtr && *spanPtr != -1 ? *spanPtr : points_.size() / 2 - 1);\n> -\tif (spanPtr && updateSpan)\n> -\t\t*spanPtr = span;\n> -\treturn points_[span].y +\n> -\t       (x - points_[span].x) * (points_[span + 1].y - points_[span].y) /\n> -\t\t       (points_[span + 1].x - points_[span].x);\n> -}\n> -\n> -int Pwl::findSpan(double x, int span) const\n> -{\n> -\t/*\n> -\t * Pwls are generally small, so linear search may well be faster than\n> -\t * binary, though could review this if large PWls start turning up.\n> -\t */\n> -\tint lastSpan = points_.size() - 2;\n> -\t/*\n> -\t * some algorithms may call us with span pointing directly at the last\n> -\t * control point\n> -\t */\n> -\tspan = std::max(0, std::min(lastSpan, span));\n> -\twhile (span < lastSpan && x >= points_[span + 1].x)\n> -\t\tspan++;\n> -\twhile (span && x < points_[span].x)\n> -\t\tspan--;\n> -\treturn span;\n> -}\n> -\n> -Pwl::PerpType Pwl::invert(Point const &xy, Point &perp, int &span,\n> -\t\t\t  const double eps) const\n> -{\n> -\tassert(span >= -1);\n> -\tbool prevOffEnd = false;\n> -\tfor (span = span + 1; span < (int)points_.size() - 1; span++) {\n> -\t\tPoint spanVec = points_[span + 1] - points_[span];\n> -\t\tdouble t = ((xy - points_[span]) % spanVec) / spanVec.len2();\n> -\t\tif (t < -eps) /* off the start of this span */\n> -\t\t{\n> -\t\t\tif (span == 0) {\n> -\t\t\t\tperp = points_[span];\n> -\t\t\t\treturn PerpType::Start;\n> -\t\t\t} else if (prevOffEnd) {\n> -\t\t\t\tperp = points_[span];\n> -\t\t\t\treturn PerpType::Vertex;\n> -\t\t\t}\n> -\t\t} else if (t > 1 + eps) /* off the end of this span */\n> -\t\t{\n> -\t\t\tif (span == (int)points_.size() - 2) {\n> -\t\t\t\tperp = points_[span + 1];\n> -\t\t\t\treturn PerpType::End;\n> -\t\t\t}\n> -\t\t\tprevOffEnd = true;\n> -\t\t} else /* a true perpendicular */\n> -\t\t{\n> -\t\t\tperp = points_[span] + spanVec * t;\n> -\t\t\treturn PerpType::Perpendicular;\n> -\t\t}\n> -\t}\n> -\treturn PerpType::None;\n> -}\n> -\n> -Pwl Pwl::inverse(bool *trueInverse, const double eps) const\n> -{\n> -\tbool appended = false, prepended = false, neither = false;\n> -\tPwl inverse;\n> -\n> -\tfor (Point const &p : points_) {\n> -\t\tif (inverse.empty())\n> -\t\t\tinverse.append(p.y, p.x, eps);\n> -\t\telse if (std::abs(inverse.points_.back().x - p.y) <= eps ||\n> -\t\t\t std::abs(inverse.points_.front().x - p.y) <= eps)\n> -\t\t\t/* do nothing */;\n> -\t\telse if (p.y > inverse.points_.back().x) {\n> -\t\t\tinverse.append(p.y, p.x, eps);\n> -\t\t\tappended = true;\n> -\t\t} else if (p.y < inverse.points_.front().x) {\n> -\t\t\tinverse.prepend(p.y, p.x, eps);\n> -\t\t\tprepended = true;\n> -\t\t} else\n> -\t\t\tneither = true;\n> -\t}\n> -\n> -\t/*\n> -\t * This is not a proper inverse if we found ourselves putting points\n> -\t * onto both ends of the inverse, or if there were points that couldn't\n> -\t * go on either.\n> -\t */\n> -\tif (trueInverse)\n> -\t\t*trueInverse = !(neither || (appended && prepended));\n> -\n> -\treturn inverse;\n> -}\n> -\n> -Pwl Pwl::compose(Pwl const &other, const double eps) const\n> -{\n> -\tdouble thisX = points_[0].x, thisY = points_[0].y;\n> -\tint thisSpan = 0, otherSpan = other.findSpan(thisY, 0);\n> -\tPwl result({ { thisX, other.eval(thisY, &otherSpan, false) } });\n> -\twhile (thisSpan != (int)points_.size() - 1) {\n> -\t\tdouble dx = points_[thisSpan + 1].x - points_[thisSpan].x,\n> -\t\t       dy = points_[thisSpan + 1].y - points_[thisSpan].y;\n> -\t\tif (std::abs(dy) > eps &&\n> -\t\t    otherSpan + 1 < (int)other.points_.size() &&\n> -\t\t    points_[thisSpan + 1].y >=\n> -\t\t\t    other.points_[otherSpan + 1].x + eps) {\n> -\t\t\t/*\n> -\t\t\t * next control point in result will be where this\n> -\t\t\t * function's y reaches the next span in other\n> -\t\t\t */\n> -\t\t\tthisX = points_[thisSpan].x +\n> -\t\t\t\t(other.points_[otherSpan + 1].x -\n> -\t\t\t\t points_[thisSpan].y) *\n> -\t\t\t\t\tdx / dy;\n> -\t\t\tthisY = other.points_[++otherSpan].x;\n> -\t\t} else if (std::abs(dy) > eps && otherSpan > 0 &&\n> -\t\t\t   points_[thisSpan + 1].y <=\n> -\t\t\t\t   other.points_[otherSpan - 1].x - eps) {\n> -\t\t\t/*\n> -\t\t\t * next control point in result will be where this\n> -\t\t\t * function's y reaches the previous span in other\n> -\t\t\t */\n> -\t\t\tthisX = points_[thisSpan].x +\n> -\t\t\t\t(other.points_[otherSpan + 1].x -\n> -\t\t\t\t points_[thisSpan].y) *\n> -\t\t\t\t\tdx / dy;\n> -\t\t\tthisY = other.points_[--otherSpan].x;\n> -\t\t} else {\n> -\t\t\t/* we stay in the same span in other */\n> -\t\t\tthisSpan++;\n> -\t\t\tthisX = points_[thisSpan].x,\n> -\t\t\tthisY = points_[thisSpan].y;\n> -\t\t}\n> -\t\tresult.append(thisX, other.eval(thisY, &otherSpan, false),\n> -\t\t\t      eps);\n> -\t}\n> -\treturn result;\n> -}\n> -\n> -void Pwl::map(std::function<void(double x, double y)> f) const\n> -{\n> -\tfor (auto &pt : points_)\n> -\t\tf(pt.x, pt.y);\n> -}\n> -\n> -void Pwl::map2(Pwl const &pwl0, Pwl const &pwl1,\n> -\t       std::function<void(double x, double y0, double y1)> f)\n> -{\n> -\tint span0 = 0, span1 = 0;\n> -\tdouble x = std::min(pwl0.points_[0].x, pwl1.points_[0].x);\n> -\tf(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));\n> -\twhile (span0 < (int)pwl0.points_.size() - 1 ||\n> -\t       span1 < (int)pwl1.points_.size() - 1) {\n> -\t\tif (span0 == (int)pwl0.points_.size() - 1)\n> -\t\t\tx = pwl1.points_[++span1].x;\n> -\t\telse if (span1 == (int)pwl1.points_.size() - 1)\n> -\t\t\tx = pwl0.points_[++span0].x;\n> -\t\telse if (pwl0.points_[span0 + 1].x > pwl1.points_[span1 + 1].x)\n> -\t\t\tx = pwl1.points_[++span1].x;\n> -\t\telse\n> -\t\t\tx = pwl0.points_[++span0].x;\n> -\t\tf(x, pwl0.eval(x, &span0, false), pwl1.eval(x, &span1, false));\n> -\t}\n> -}\n> -\n> -Pwl Pwl::combine(Pwl const &pwl0, Pwl const &pwl1,\n> -\t\t std::function<double(double x, double y0, double y1)> f,\n> -\t\t const double eps)\n> -{\n> -\tPwl result;\n> -\tmap2(pwl0, pwl1, [&](double x, double y0, double y1) {\n> -\t\tresult.append(x, f(x, y0, y1), eps);\n> -\t});\n> -\treturn result;\n> -}\n> -\n> -void Pwl::matchDomain(Interval const &domain, bool clip, const double eps)\n> -{\n> -\tint span = 0;\n> -\tprepend(domain.start, eval(clip ? points_[0].x : domain.start, &span),\n> -\t\teps);\n> -\tspan = points_.size() - 2;\n> -\tappend(domain.end, eval(clip ? points_.back().x : domain.end, &span),\n> -\t       eps);\n> -}\n> -\n> -Pwl &Pwl::operator*=(double d)\n> -{\n> -\tfor (auto &pt : points_)\n> -\t\tpt.y *= d;\n> -\treturn *this;\n> -}\n> -\n> -void Pwl::debug(FILE *fp) const\n> -{\n> -\tfprintf(fp, \"Pwl {\\n\");\n> -\tfor (auto &p : points_)\n> -\t\tfprintf(fp, \"\\t(%g, %g)\\n\", p.x, p.y);\n> -\tfprintf(fp, \"}\\n\");\n> -}\n> diff --git a/src/ipa/rpi/controller/pwl.h b/src/ipa/rpi/controller/pwl.h\n> deleted file mode 100644\n> index aacf6039..00000000\n> --- a/src/ipa/rpi/controller/pwl.h\n> +++ /dev/null\n> @@ -1,127 +0,0 @@\n> -/* SPDX-License-Identifier: BSD-2-Clause */\n> -/*\n> - * Copyright (C) 2019, Raspberry Pi Ltd\n> - *\n> - * pwl.h - piecewise linear functions interface\n> - */\n> -#pragma once\n> -\n> -#include <functional>\n> -#include <math.h>\n> -#include <vector>\n> -\n> -#include \"libcamera/internal/yaml_parser.h\"\n> -\n> -namespace RPiController {\n> -\n> -class Pwl\n> -{\n> -public:\n> -\tstruct Interval {\n> -\t\tInterval(double _start, double _end)\n> -\t\t\t: start(_start), end(_end)\n> -\t\t{\n> -\t\t}\n> -\t\tdouble start, end;\n> -\t\tbool contains(double value)\n> -\t\t{\n> -\t\t\treturn value >= start && value <= end;\n> -\t\t}\n> -\t\tdouble clip(double value)\n> -\t\t{\n> -\t\t\treturn value < start ? start\n> -\t\t\t\t\t     : (value > end ? end : value);\n> -\t\t}\n> -\t\tdouble len() const { return end - start; }\n> -\t};\n> -\tstruct Point {\n> -\t\tPoint() : x(0), y(0) {}\n> -\t\tPoint(double _x, double _y)\n> -\t\t\t: x(_x), y(_y) {}\n> -\t\tdouble x, y;\n> -\t\tPoint operator-(Point const &p) const\n> -\t\t{\n> -\t\t\treturn Point(x - p.x, y - p.y);\n> -\t\t}\n> -\t\tPoint operator+(Point const &p) const\n> -\t\t{\n> -\t\t\treturn Point(x + p.x, y + p.y);\n> -\t\t}\n> -\t\tdouble operator%(Point const &p) const\n> -\t\t{\n> -\t\t\treturn x * p.x + y * p.y;\n> -\t\t}\n> -\t\tPoint operator*(double f) const { return Point(x * f, y * f); }\n> -\t\tPoint operator/(double f) const { return Point(x / f, y / f); }\n> -\t\tdouble len2() const { return x * x + y * y; }\n> -\t\tdouble len() const { return sqrt(len2()); }\n> -\t};\n> -\tPwl() {}\n> -\tPwl(std::vector<Point> const &points) : points_(points) {}\n> -\tint read(const libcamera::YamlObject &params);\n> -\tvoid append(double x, double y, const double eps = 1e-6);\n> -\tvoid prepend(double x, double y, const double eps = 1e-6);\n> -\tInterval domain() const;\n> -\tInterval range() const;\n> -\tbool empty() const;\n> -\t/*\n> -\t * Evaluate Pwl, optionally supplying an initial guess for the\n> -\t * \"span\". The \"span\" may be optionally be updated.  If you want to know\n> -\t * the \"span\" value but don't have an initial guess you can set it to\n> -\t * -1.\n> -\t */\n> -\tdouble eval(double x, int *spanPtr = nullptr,\n> -\t\t    bool updateSpan = true) const;\n> -\t/*\n> -\t * Find perpendicular closest to xy, starting from span+1 so you can\n> -\t * call it repeatedly to check for multiple closest points (set span to\n> -\t * -1 on the first call). Also returns \"pseudo\" perpendiculars; see\n> -\t * PerpType enum.\n> -\t */\n> -\tenum class PerpType {\n> -\t\tNone, /* no perpendicular found */\n> -\t\tStart, /* start of Pwl is closest point */\n> -\t\tEnd, /* end of Pwl is closest point */\n> -\t\tVertex, /* vertex of Pwl is closest point */\n> -\t\tPerpendicular /* true perpendicular found */\n> -\t};\n> -\tPerpType invert(Point const &xy, Point &perp, int &span,\n> -\t\t\tconst double eps = 1e-6) const;\n> -\t/*\n> -\t * Compute the inverse function. Indicate if it is a proper (true)\n> -\t * inverse, or only a best effort (e.g. input was non-monotonic).\n> -\t */\n> -\tPwl inverse(bool *trueInverse = nullptr, const double eps = 1e-6) const;\n> -\t/* Compose two Pwls together, doing \"this\" first and \"other\" after. */\n> -\tPwl compose(Pwl const &other, const double eps = 1e-6) const;\n> -\t/* Apply function to (x,y) values at every control point. */\n> -\tvoid map(std::function<void(double x, double y)> f) const;\n> -\t/*\n> -\t * Apply function to (x, y0, y1) values wherever either Pwl has a\n> -\t * control point.\n> -\t */\n> -\tstatic void map2(Pwl const &pwl0, Pwl const &pwl1,\n> -\t\t\t std::function<void(double x, double y0, double y1)> f);\n> -\t/*\n> -\t * Combine two Pwls, meaning we create a new Pwl where the y values are\n> -\t * given by running f wherever either has a knot.\n> -\t */\n> -\tstatic Pwl\n> -\tcombine(Pwl const &pwl0, Pwl const &pwl1,\n> -\t\tstd::function<double(double x, double y0, double y1)> f,\n> -\t\tconst double eps = 1e-6);\n> -\t/*\n> -\t * Make \"this\" match (at least) the given domain. Any extension my be\n> -\t * clipped or linear.\n> -\t */\n> -\tvoid matchDomain(Interval const &domain, bool clip = true,\n> -\t\t\t const double eps = 1e-6);\n> -\tPwl &operator*=(double d);\n> -\tvoid debug(FILE *fp = stdout) const;\n> -\n> -private:\n> -\tint findSpan(double x, int span) const;\n> -\tstd::vector<Point> points_;\n> -};\n> -\n> -} /* namespace RPiController */\n> diff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp\n> index ed0c8a94..d54cee69 100644\n> --- a/src/ipa/rpi/controller/rpi/af.cpp\n> +++ b/src/ipa/rpi/controller/rpi/af.cpp\n> @@ -139,7 +139,7 @@ int Af::CfgParams::read(const libcamera::YamlObject &params)\n>  \treadNumber<uint32_t>(skipFrames, params, \"skip_frames\");\n>  \n>  \tif (params.contains(\"map\"))\n> -\t\tmap.read(params[\"map\"]);\n> +\t\tmap.readYaml(params[\"map\"]);\n>  \telse\n>  \t\tLOG(RPiAf, Warning) << \"No map defined\";\n>  \n> @@ -721,7 +721,7 @@ bool Af::setLensPosition(double dioptres, int *hwpos)\n>  \n>  \tif (mode_ == AfModeManual) {\n>  \t\tLOG(RPiAf, Debug) << \"setLensPosition: \" << dioptres;\n> -\t\tftarget_ = cfg_.map.domain().clip(dioptres);\n> +\t\tftarget_ = cfg_.map.domain().clamp(dioptres);\n>  \t\tchanged = !(initted_ && fsmooth_ == ftarget_);\n>  \t\tupdateLensPosition();\n>  \t}\n> diff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h\n> index 6d2bae67..a6b2ad07 100644\n> --- a/src/ipa/rpi/controller/rpi/af.h\n> +++ b/src/ipa/rpi/controller/rpi/af.h\n> @@ -9,7 +9,8 @@\n>  #include \"../af_algorithm.h\"\n>  #include \"../af_status.h\"\n>  #include \"../pdaf_data.h\"\n> -#include \"../pwl.h\"\n> +\n> +#include \"libipa/pwl.h\"\n>  \n>  /*\n>   * This algorithm implements a hybrid of CDAF and PDAF, favouring PDAF.\n> @@ -100,7 +101,7 @@ private:\n>  \t\tuint32_t confThresh;\t       \t/* PDAF confidence cell min (sensor-specific) */\n>  \t\tuint32_t confClip;\t       \t/* PDAF confidence cell max (sensor-specific) */\n>  \t\tuint32_t skipFrames;\t       \t/* frames to skip at start or modeswitch */\n> -\t\tPwl map;\t\t       \t/* converts dioptres -> lens driver position */\n> +\t\tlibcamera::ipa::Pwl map;       \t/* converts dioptres -> lens driver position */\n>  \n>  \t\tCfgParams();\n>  \t\tint read(const libcamera::YamlObject &params);\n> diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp\n> index 8116c6c1..e6d292f9 100644\n> --- a/src/ipa/rpi/controller/rpi/agc_channel.cpp\n> +++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp\n> @@ -130,7 +130,7 @@ int AgcConstraint::read(const libcamera::YamlObject &params)\n>  \t\treturn -EINVAL;\n>  \tqHi = *value;\n>  \n> -\treturn yTarget.read(params[\"y_target\"]);\n> +\treturn yTarget.readYaml(params[\"y_target\"]);\n>  }\n>  \n>  static std::tuple<int, AgcConstraintMode>\n> @@ -237,7 +237,7 @@ int AgcConfig::read(const libcamera::YamlObject &params)\n>  \t\t\treturn ret;\n>  \t}\n>  \n> -\tret = yTarget.read(params[\"y_target\"]);\n> +\tret = yTarget.readYaml(params[\"y_target\"]);\n>  \tif (ret)\n>  \t\treturn ret;\n>  \n> @@ -715,7 +715,7 @@ static constexpr double EvGainYTargetLimit = 0.9;\n>  static double constraintComputeGain(AgcConstraint &c, const Histogram &h, double lux,\n>  \t\t\t\t    double evGain, double &targetY)\n>  {\n> -\ttargetY = c.yTarget.eval(c.yTarget.domain().clip(lux));\n> +\ttargetY = c.yTarget.eval(c.yTarget.domain().clamp(lux));\n>  \ttargetY = std::min(EvGainYTargetLimit, targetY * evGain);\n>  \tdouble iqm = h.interQuantileMean(c.qLo, c.qHi);\n>  \treturn (targetY * h.bins()) / iqm;\n> @@ -734,7 +734,7 @@ void AgcChannel::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,\n>  \t * The initial gain and target_Y come from some of the regions. After\n>  \t * that we consider the histogram constraints.\n>  \t */\n> -\ttargetY = config_.yTarget.eval(config_.yTarget.domain().clip(lux.lux));\n> +\ttargetY = config_.yTarget.eval(config_.yTarget.domain().clamp(lux.lux));\n>  \ttargetY = std::min(EvGainYTargetLimit, targetY * evGain);\n>  \n>  \t/*\n> diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h\n> index 4cf7233e..157b3baf 100644\n> --- a/src/ipa/rpi/controller/rpi/agc_channel.h\n> +++ b/src/ipa/rpi/controller/rpi/agc_channel.h\n> @@ -12,10 +12,11 @@\n>  \n>  #include <libcamera/base/utils.h>\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"../agc_status.h\"\n>  #include \"../awb_status.h\"\n>  #include \"../controller.h\"\n> -#include \"../pwl.h\"\n>  \n>  /* This is our implementation of AGC. */\n>  \n> @@ -40,7 +41,7 @@ struct AgcConstraint {\n>  \tBound bound;\n>  \tdouble qLo;\n>  \tdouble qHi;\n> -\tPwl yTarget;\n> +\tlibcamera::ipa::Pwl yTarget;\n>  \tint read(const libcamera::YamlObject &params);\n>  };\n>  \n> @@ -61,7 +62,7 @@ struct AgcConfig {\n>  \tstd::map<std::string, AgcExposureMode> exposureModes;\n>  \tstd::map<std::string, AgcConstraintMode> constraintModes;\n>  \tstd::vector<AgcChannelConstraint> channelConstraints;\n> -\tPwl yTarget;\n> +\tlibcamera::ipa::Pwl yTarget;\n>  \tdouble speed;\n>  \tuint16_t startupFrames;\n>  \tunsigned int convergenceFrames;\n> diff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp\n> index dde5785a..f4093910 100644\n> --- a/src/ipa/rpi/controller/rpi/awb.cpp\n> +++ b/src/ipa/rpi/controller/rpi/awb.cpp\n> @@ -49,10 +49,10 @@ int AwbPrior::read(const libcamera::YamlObject &params)\n>  \t\treturn -EINVAL;\n>  \tlux = *value;\n>  \n> -\treturn prior.read(params[\"prior\"]);\n> +\treturn prior.readYaml(params[\"prior\"]);\n>  }\n>  \n> -static int readCtCurve(Pwl &ctR, Pwl &ctB, const libcamera::YamlObject &params)\n> +static int readCtCurve(ipa::Pwl &ctR, ipa::Pwl &ctB, const libcamera::YamlObject &params)\n>  {\n>  \tif (params.size() % 3) {\n>  \t\tLOG(RPiAwb, Error) << \"AwbConfig: incomplete CT curve entry\";\n> @@ -207,7 +207,7 @@ void Awb::initialise()\n>  \t * them.\n>  \t */\n>  \tif (!config_.ctR.empty() && !config_.ctB.empty()) {\n> -\t\tsyncResults_.temperatureK = config_.ctR.domain().clip(4000);\n> +\t\tsyncResults_.temperatureK = config_.ctR.domain().clamp(4000);\n>  \t\tsyncResults_.gainR = 1.0 / config_.ctR.eval(syncResults_.temperatureK);\n>  \t\tsyncResults_.gainG = 1.0;\n>  \t\tsyncResults_.gainB = 1.0 / config_.ctB.eval(syncResults_.temperatureK);\n> @@ -273,8 +273,8 @@ void Awb::setManualGains(double manualR, double manualB)\n>  \t\tsyncResults_.gainB = prevSyncResults_.gainB = manualB_;\n>  \t\tif (config_.bayes) {\n>  \t\t\t/* Also estimate the best corresponding colour temperature from the curves. */\n> -\t\t\tdouble ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clip(1 / manualR_));\n> -\t\t\tdouble ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clip(1 / manualB_));\n> +\t\t\tdouble ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clamp(1 / manualR_));\n> +\t\t\tdouble ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clamp(1 / manualB_));\n>  \t\t\tprevSyncResults_.temperatureK = (ctR + ctB) / 2;\n>  \t\t\tsyncResults_.temperatureK = prevSyncResults_.temperatureK;\n>  \t\t}\n> @@ -468,7 +468,7 @@ double Awb::computeDelta2Sum(double gainR, double gainB)\n>  \treturn delta2Sum;\n>  }\n>  \n> -Pwl Awb::interpolatePrior()\n> +ipa::Pwl Awb::interpolatePrior()\n>  {\n>  \t/*\n>  \t * Interpolate the prior log likelihood function for our current lux\n> @@ -485,7 +485,7 @@ Pwl Awb::interpolatePrior()\n>  \t\t\tidx++;\n>  \t\tdouble lux0 = config_.priors[idx].lux,\n>  \t\t       lux1 = config_.priors[idx + 1].lux;\n> -\t\treturn Pwl::combine(config_.priors[idx].prior,\n> +\t\treturn ipa::Pwl::combine(config_.priors[idx].prior,\n>  \t\t\t\t    config_.priors[idx + 1].prior,\n>  \t\t\t\t    [&](double /*x*/, double y0, double y1) {\n>  \t\t\t\t\t    return y0 + (y1 - y0) *\n> @@ -494,15 +494,15 @@ Pwl Awb::interpolatePrior()\n>  \t}\n>  }\n>  \n> -static double interpolateQuadatric(Pwl::Point const &a, Pwl::Point const &b,\n> -\t\t\t\t   Pwl::Point const &c)\n> +static double interpolateQuadatric(FPoint const &a, FPoint const &b,\n> +\t\t\t\t   FPoint const &c)\n>  {\n>  \t/*\n>  \t * Given 3 points on a curve, find the extremum of the function in that\n>  \t * interval by fitting a quadratic.\n>  \t */\n>  \tconst double eps = 1e-3;\n> -\tPwl::Point ca = c - a, ba = b - a;\n> +\tFPoint ca = c - a, ba = b - a;\n>  \tdouble denominator = 2 * (ba.y * ca.x - ca.y * ba.x);\n>  \tif (abs(denominator) > eps) {\n>  \t\tdouble numerator = ba.y * ca.x * ca.x - ca.y * ba.x * ba.x;\n> @@ -513,7 +513,7 @@ static double interpolateQuadatric(Pwl::Point const &a, Pwl::Point const &b,\n>  \treturn a.y < c.y - eps ? a.x : (c.y < a.y - eps ? c.x : b.x);\n>  }\n>  \n> -double Awb::coarseSearch(Pwl const &prior)\n> +double Awb::coarseSearch(ipa::Pwl const &prior)\n>  {\n>  \tpoints_.clear(); /* assume doesn't deallocate memory */\n>  \tsize_t bestPoint = 0;\n> @@ -525,14 +525,14 @@ double Awb::coarseSearch(Pwl const &prior)\n>  \t\tdouble b = config_.ctB.eval(t, &spanB);\n>  \t\tdouble gainR = 1 / r, gainB = 1 / b;\n>  \t\tdouble delta2Sum = computeDelta2Sum(gainR, gainB);\n> -\t\tdouble priorLogLikelihood = prior.eval(prior.domain().clip(t));\n> +\t\tdouble priorLogLikelihood = prior.eval(prior.domain().clamp(t));\n>  \t\tdouble finalLogLikelihood = delta2Sum - priorLogLikelihood;\n>  \t\tLOG(RPiAwb, Debug)\n>  \t\t\t<< \"t: \" << t << \" gain R \" << gainR << \" gain B \"\n>  \t\t\t<< gainB << \" delta2_sum \" << delta2Sum\n>  \t\t\t<< \" prior \" << priorLogLikelihood << \" final \"\n>  \t\t\t<< finalLogLikelihood;\n> -\t\tpoints_.push_back(Pwl::Point(t, finalLogLikelihood));\n> +\t\tpoints_.push_back(FPoint(t, finalLogLikelihood));\n>  \t\tif (points_.back().y < points_[bestPoint].y)\n>  \t\t\tbestPoint = points_.size() - 1;\n>  \t\tif (t == mode_->ctHi)\n> @@ -559,7 +559,7 @@ double Awb::coarseSearch(Pwl const &prior)\n>  \treturn t;\n>  }\n>  \n> -void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)\n> +void Awb::fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior)\n>  {\n>  \tint spanR = -1, spanB = -1;\n>  \tconfig_.ctR.eval(t, &spanR);\n> @@ -570,7 +570,7 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)\n>  \t\t       config_.ctR.eval(t - nsteps * step, &spanR);\n>  \tdouble bDiff = config_.ctB.eval(t + nsteps * step, &spanB) -\n>  \t\t       config_.ctB.eval(t - nsteps * step, &spanB);\n> -\tPwl::Point transverse(bDiff, -rDiff);\n> +\tFPoint transverse(bDiff, -rDiff);\n>  \tif (transverse.len2() < 1e-6)\n>  \t\treturn;\n>  \t/*\n> @@ -592,17 +592,17 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)\n>  \tfor (int i = -nsteps; i <= nsteps; i++) {\n>  \t\tdouble tTest = t + i * step;\n>  \t\tdouble priorLogLikelihood =\n> -\t\t\tprior.eval(prior.domain().clip(tTest));\n> +\t\t\tprior.eval(prior.domain().clamp(tTest));\n>  \t\tdouble rCurve = config_.ctR.eval(tTest, &spanR);\n>  \t\tdouble bCurve = config_.ctB.eval(tTest, &spanB);\n>  \t\t/* x will be distance off the curve, y the log likelihood there */\n> -\t\tPwl::Point points[maxNumDeltas];\n> +\t\tFPoint points[maxNumDeltas];\n>  \t\tint bestPoint = 0;\n>  \t\t/* Take some measurements transversely *off* the CT curve. */\n>  \t\tfor (int j = 0; j < numDeltas; j++) {\n>  \t\t\tpoints[j].x = -config_.transverseNeg +\n>  \t\t\t\t      (transverseRange * j) / (numDeltas - 1);\n> -\t\t\tPwl::Point rbTest = Pwl::Point(rCurve, bCurve) +\n> +\t\t\tFPoint rbTest = FPoint(rCurve, bCurve) +\n>  \t\t\t\t\t    transverse * points[j].x;\n>  \t\t\tdouble rTest = rbTest.x, bTest = rbTest.y;\n>  \t\t\tdouble gainR = 1 / rTest, gainB = 1 / bTest;\n> @@ -619,7 +619,7 @@ void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)\n>  \t\t * now let's do a quadratic interpolation for the best result.\n>  \t\t */\n>  \t\tbestPoint = std::max(1, std::min(bestPoint, numDeltas - 2));\n> -\t\tPwl::Point rbTest = Pwl::Point(rCurve, bCurve) +\n> +\t\tFPoint rbTest = FPoint(rCurve, bCurve) +\n>  \t\t\t\t\ttransverse * interpolateQuadatric(points[bestPoint - 1],\n>  \t\t\t\t\t\t\t\t\tpoints[bestPoint],\n>  \t\t\t\t\t\t\t\t\tpoints[bestPoint + 1]);\n> @@ -653,7 +653,7 @@ void Awb::awbBayes()\n>  \t * Get the current prior, and scale according to how many zones are\n>  \t * valid... not entirely sure about this.\n>  \t */\n> -\tPwl prior = interpolatePrior();\n> +\tipa::Pwl prior = interpolatePrior();\n>  \tprior *= zones_.size() / (double)(statistics_->awbRegions.numRegions());\n>  \tprior.map([](double x, double y) {\n>  \t\tLOG(RPiAwb, Debug) << \"(\" << x << \",\" << y << \")\";\n> diff --git a/src/ipa/rpi/controller/rpi/awb.h b/src/ipa/rpi/controller/rpi/awb.h\n> index cde6a62f..681353fe 100644\n> --- a/src/ipa/rpi/controller/rpi/awb.h\n> +++ b/src/ipa/rpi/controller/rpi/awb.h\n> @@ -10,11 +10,14 @@\n>  #include <condition_variable>\n>  #include <thread>\n>  \n> +#include <libcamera/geometry.h>\n> +\n>  #include \"../awb_algorithm.h\"\n> -#include \"../pwl.h\"\n>  #include \"../awb_status.h\"\n>  #include \"../statistics.h\"\n>  \n> +#include \"libipa/pwl.h\"\n> +\n>  namespace RPiController {\n>  \n>  /* Control algorithm to perform AWB calculations. */\n> @@ -28,7 +31,7 @@ struct AwbMode {\n>  struct AwbPrior {\n>  \tint read(const libcamera::YamlObject &params);\n>  \tdouble lux; /* lux level */\n> -\tPwl prior; /* maps CT to prior log likelihood for this lux level */\n> +\tlibcamera::ipa::Pwl prior; /* maps CT to prior log likelihood for this lux level */\n>  };\n>  \n>  struct AwbConfig {\n> @@ -41,10 +44,10 @@ struct AwbConfig {\n>  \tunsigned int convergenceFrames; /* approx number of frames to converge */\n>  \tdouble speed; /* IIR filter speed applied to algorithm results */\n>  \tbool fast; /* \"fast\" mode uses a 16x16 rather than 32x32 grid */\n> -\tPwl ctR; /* function maps CT to r (= R/G) */\n> -\tPwl ctB; /* function maps CT to b (= B/G) */\n> -\tPwl ctRInverse; /* inverse of ctR */\n> -\tPwl ctBInverse; /* inverse of ctB */\n> +\tlibcamera::ipa::Pwl ctR; /* function maps CT to r (= R/G) */\n> +\tlibcamera::ipa::Pwl ctB; /* function maps CT to b (= B/G) */\n> +\tlibcamera::ipa::Pwl ctRInverse; /* inverse of ctR */\n> +\tlibcamera::ipa::Pwl ctBInverse; /* inverse of ctB */\n>  \t/* table of illuminant priors at different lux levels */\n>  \tstd::vector<AwbPrior> priors;\n>  \t/* AWB \"modes\" (determines the search range) */\n> @@ -161,11 +164,11 @@ private:\n>  \tvoid awbGrey();\n>  \tvoid prepareStats();\n>  \tdouble computeDelta2Sum(double gainR, double gainB);\n> -\tPwl interpolatePrior();\n> -\tdouble coarseSearch(Pwl const &prior);\n> -\tvoid fineSearch(double &t, double &r, double &b, Pwl const &prior);\n> +\tlibcamera::ipa::Pwl interpolatePrior();\n> +\tdouble coarseSearch(libcamera::ipa::Pwl const &prior);\n> +\tvoid fineSearch(double &t, double &r, double &b, libcamera::ipa::Pwl const &prior);\n>  \tstd::vector<RGB> zones_;\n> -\tstd::vector<Pwl::Point> points_;\n> +\tstd::vector<libcamera::FPoint> points_;\n>  \t/* manual r setting */\n>  \tdouble manualR_;\n>  \t/* manual b setting */\n> diff --git a/src/ipa/rpi/controller/rpi/ccm.cpp b/src/ipa/rpi/controller/rpi/ccm.cpp\n> index 2e2e6664..68f9cff6 100644\n> --- a/src/ipa/rpi/controller/rpi/ccm.cpp\n> +++ b/src/ipa/rpi/controller/rpi/ccm.cpp\n> @@ -71,7 +71,7 @@ int Ccm::read(const libcamera::YamlObject &params)\n>  \tint ret;\n>  \n>  \tif (params.contains(\"saturation\")) {\n> -\t\tret = config_.saturation.read(params[\"saturation\"]);\n> +\t\tret = config_.saturation.readYaml(params[\"saturation\"]);\n>  \t\tif (ret)\n>  \t\t\treturn ret;\n>  \t}\n> @@ -172,7 +172,7 @@ void Ccm::prepare(Metadata *imageMetadata)\n>  \tccmStatus.saturation = saturation;\n>  \tif (!config_.saturation.empty())\n>  \t\tsaturation *= config_.saturation.eval(\n> -\t\t\tconfig_.saturation.domain().clip(lux.lux));\n> +\t\t\tconfig_.saturation.domain().clamp(lux.lux));\n>  \tccm = applySaturation(ccm, saturation);\n>  \tfor (int j = 0; j < 3; j++)\n>  \t\tfor (int i = 0; i < 3; i++)\n> diff --git a/src/ipa/rpi/controller/rpi/ccm.h b/src/ipa/rpi/controller/rpi/ccm.h\n> index 286d0b33..e5042176 100644\n> --- a/src/ipa/rpi/controller/rpi/ccm.h\n> +++ b/src/ipa/rpi/controller/rpi/ccm.h\n> @@ -8,8 +8,9 @@\n>  \n>  #include <vector>\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"../ccm_algorithm.h\"\n> -#include \"../pwl.h\"\n>  \n>  namespace RPiController {\n>  \n> @@ -54,7 +55,7 @@ struct CtCcm {\n>  \n>  struct CcmConfig {\n>  \tstd::vector<CtCcm> ccms;\n> -\tPwl saturation;\n> +\tlibcamera::ipa::Pwl saturation;\n>  };\n>  \n>  class Ccm : public CcmAlgorithm\n> diff --git a/src/ipa/rpi/controller/rpi/contrast.cpp b/src/ipa/rpi/controller/rpi/contrast.cpp\n> index 4e038a02..7ba6cef0 100644\n> --- a/src/ipa/rpi/controller/rpi/contrast.cpp\n> +++ b/src/ipa/rpi/controller/rpi/contrast.cpp\n> @@ -53,7 +53,7 @@ int Contrast::read(const libcamera::YamlObject &params)\n>  \tconfig_.hiHistogram = params[\"hi_histogram\"].get<double>(0.95);\n>  \tconfig_.hiLevel = params[\"hi_level\"].get<double>(0.95);\n>  \tconfig_.hiMax = params[\"hi_max\"].get<double>(2000);\n> -\treturn config_.gammaCurve.read(params[\"gamma_curve\"]);\n> +\treturn config_.gammaCurve.readYaml(params[\"gamma_curve\"]);\n>  }\n>  \n>  void Contrast::setBrightness(double brightness)\n> @@ -92,10 +92,10 @@ void Contrast::prepare(Metadata *imageMetadata)\n>  \timageMetadata->set(\"contrast.status\", status_);\n>  }\n>  \n> -Pwl computeStretchCurve(Histogram const &histogram,\n> +ipa::Pwl computeStretchCurve(Histogram const &histogram,\n>  \t\t\tContrastConfig const &config)\n>  {\n> -\tPwl enhance;\n> +\tipa::Pwl enhance;\n>  \tenhance.append(0, 0);\n>  \t/*\n>  \t * If the start of the histogram is rather empty, try to pull it down a\n> @@ -136,10 +136,10 @@ Pwl computeStretchCurve(Histogram const &histogram,\n>  \treturn enhance;\n>  }\n>  \n> -Pwl applyManualContrast(Pwl const &gammaCurve, double brightness,\n> -\t\t\tdouble contrast)\n> +ipa::Pwl applyManualContrast(ipa::Pwl const &gammaCurve, double brightness,\n> +\t\t\t     double contrast)\n>  {\n> -\tPwl newGammaCurve;\n> +\tipa::Pwl newGammaCurve;\n>  \tLOG(RPiContrast, Debug)\n>  \t\t<< \"Manual brightness \" << brightness << \" contrast \" << contrast;\n>  \tgammaCurve.map([&](double x, double y) {\n> @@ -160,7 +160,7 @@ void Contrast::process(StatisticsPtr &stats,\n>  \t * ways: 1. Adjust the gamma curve so as to pull the start of the\n>  \t * histogram down, and possibly push the end up.\n>  \t */\n> -\tPwl gammaCurve = config_.gammaCurve;\n> +\tipa::Pwl gammaCurve = config_.gammaCurve;\n>  \tif (ceEnable_) {\n>  \t\tif (config_.loMax != 0 || config_.hiMax != 0)\n>  \t\t\tgammaCurve = computeStretchCurve(histogram, config_).compose(gammaCurve);\n> diff --git a/src/ipa/rpi/controller/rpi/contrast.h b/src/ipa/rpi/controller/rpi/contrast.h\n> index 59aa70dc..c61bfb4d 100644\n> --- a/src/ipa/rpi/controller/rpi/contrast.h\n> +++ b/src/ipa/rpi/controller/rpi/contrast.h\n> @@ -8,8 +8,9 @@\n>  \n>  #include <mutex>\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"../contrast_algorithm.h\"\n> -#include \"../pwl.h\"\n>  \n>  namespace RPiController {\n>  \n> @@ -26,7 +27,7 @@ struct ContrastConfig {\n>  \tdouble hiHistogram;\n>  \tdouble hiLevel;\n>  \tdouble hiMax;\n> -\tPwl gammaCurve;\n> +\tlibcamera::ipa::Pwl gammaCurve;\n>  };\n>  \n>  class Contrast : public ContrastAlgorithm\n> diff --git a/src/ipa/rpi/controller/rpi/geq.cpp b/src/ipa/rpi/controller/rpi/geq.cpp\n> index 510870e9..a0c350ec 100644\n> --- a/src/ipa/rpi/controller/rpi/geq.cpp\n> +++ b/src/ipa/rpi/controller/rpi/geq.cpp\n> @@ -9,7 +9,6 @@\n>  \n>  #include \"../device_status.h\"\n>  #include \"../lux_status.h\"\n> -#include \"../pwl.h\"\n>  \n>  #include \"geq.h\"\n>  \n> @@ -45,7 +44,7 @@ int Geq::read(const libcamera::YamlObject &params)\n>  \t}\n>  \n>  \tif (params.contains(\"strength\")) {\n> -\t\tint ret = config_.strength.read(params[\"strength\"]);\n> +\t\tint ret = config_.strength.readYaml(params[\"strength\"]);\n>  \t\tif (ret)\n>  \t\t\treturn ret;\n>  \t}\n> @@ -67,7 +66,7 @@ void Geq::prepare(Metadata *imageMetadata)\n>  \tGeqStatus geqStatus = {};\n>  \tdouble strength = config_.strength.empty()\n>  \t\t\t? 1.0\n> -\t\t\t: config_.strength.eval(config_.strength.domain().clip(luxStatus.lux));\n> +\t\t\t: config_.strength.eval(config_.strength.domain().clamp(luxStatus.lux));\n>  \tstrength *= deviceStatus.analogueGain;\n>  \tdouble offset = config_.offset * strength;\n>  \tdouble slope = config_.slope * strength;\n> diff --git a/src/ipa/rpi/controller/rpi/geq.h b/src/ipa/rpi/controller/rpi/geq.h\n> index ee3a52ff..5ae29edc 100644\n> --- a/src/ipa/rpi/controller/rpi/geq.h\n> +++ b/src/ipa/rpi/controller/rpi/geq.h\n> @@ -6,6 +6,8 @@\n>   */\n>  #pragma once\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"../algorithm.h\"\n>  #include \"../geq_status.h\"\n>  \n> @@ -16,7 +18,7 @@ namespace RPiController {\n>  struct GeqConfig {\n>  \tuint16_t offset;\n>  \tdouble slope;\n> -\tPwl strength; /* lux to strength factor */\n> +\tlibcamera::ipa::Pwl strength; /* lux to strength factor */\n>  };\n>  \n>  class Geq : public Algorithm\n> diff --git a/src/ipa/rpi/controller/rpi/hdr.cpp b/src/ipa/rpi/controller/rpi/hdr.cpp\n> index fb580548..b3ff2e25 100644\n> --- a/src/ipa/rpi/controller/rpi/hdr.cpp\n> +++ b/src/ipa/rpi/controller/rpi/hdr.cpp\n> @@ -40,7 +40,7 @@ void HdrConfig::read(const libcamera::YamlObject &params, const std::string &mod\n>  \n>  \t/* Lens shading related parameters. */\n>  \tif (params.contains(\"spatial_gain\")) {\n> -\t\tspatialGain.read(params[\"spatial_gain\"]);\n> +\t\tspatialGain.readYaml(params[\"spatial_gain\"]);\n>  \t\tdiffusion = params[\"diffusion\"].get<unsigned int>(3);\n>  \t\t/* Clip to an arbitrary limit just to stop typos from killing the system! */\n>  \t\tconst unsigned int MAX_DIFFUSION = 15;\n> @@ -57,7 +57,7 @@ void HdrConfig::read(const libcamera::YamlObject &params, const std::string &mod\n>  \tiirStrength = params[\"iir_strength\"].get<double>(8.0);\n>  \tstrength = params[\"strength\"].get<double>(1.5);\n>  \tif (tonemapEnable)\n> -\t\ttonemap.read(params[\"tonemap\"]);\n> +\t\ttonemap.readYaml(params[\"tonemap\"]);\n>  \n>  \t/* Read any stitch parameters. */\n>  \tstitchEnable = params[\"stitch_enable\"].get<int>(0);\n> @@ -183,7 +183,7 @@ bool Hdr::updateTonemap([[maybe_unused]] StatisticsPtr &stats, HdrConfig &config\n>  \t/* When there's a change of HDR mode we start over with a new tonemap curve. */\n>  \tif (delayedStatus_.mode != previousMode_) {\n>  \t\tpreviousMode_ = delayedStatus_.mode;\n> -\t\ttonemap_ = Pwl();\n> +\t\ttonemap_ = ipa::Pwl();\n>  \t}\n>  \n>  \t/* No tonemapping. No need to output a tonemap.status. */\n> diff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h\n> index 980aa3d1..991fa690 100644\n> --- a/src/ipa/rpi/controller/rpi/hdr.h\n> +++ b/src/ipa/rpi/controller/rpi/hdr.h\n> @@ -12,9 +12,10 @@\n>  \n>  #include <libcamera/geometry.h>\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"../hdr_algorithm.h\"\n>  #include \"../hdr_status.h\"\n> -#include \"../pwl.h\"\n>  \n>  /* This is our implementation of an HDR algorithm. */\n>  \n> @@ -26,7 +27,7 @@ struct HdrConfig {\n>  \tstd::map<unsigned int, std::string> channelMap;\n>  \n>  \t/* Lens shading related parameters. */\n> -\tPwl spatialGain; /* Brightness to gain curve for different image regions. */\n> +\tlibcamera::ipa::Pwl spatialGain; /* Brightness to gain curve for different image regions. */\n>  \tunsigned int diffusion; /* How much to diffuse the gain spatially. */\n>  \n>  \t/* Tonemap related parameters. */\n> @@ -35,7 +36,7 @@ struct HdrConfig {\n>  \tdouble detailSlope;\n>  \tdouble iirStrength;\n>  \tdouble strength;\n> -\tPwl tonemap;\n> +\tlibcamera::ipa::Pwl tonemap;\n>  \n>  \t/* Stitch related parameters. */\n>  \tbool stitchEnable;\n> @@ -67,7 +68,7 @@ private:\n>  \tHdrStatus status_; /* track the current HDR mode and channel */\n>  \tHdrStatus delayedStatus_; /* track the delayed HDR mode and channel */\n>  \tstd::string previousMode_;\n> -\tPwl tonemap_;\n> +\tlibcamera::ipa::Pwl tonemap_;\n>  \tlibcamera::Size regions_; /* stats regions */\n>  \tunsigned int numRegions_; /* total number of stats regions */\n>  \tstd::vector<double> gains_[2];\n> diff --git a/src/ipa/rpi/controller/rpi/tonemap.cpp b/src/ipa/rpi/controller/rpi/tonemap.cpp\n> index 5f8b2bf2..f7a47fe0 100644\n> --- a/src/ipa/rpi/controller/rpi/tonemap.cpp\n> +++ b/src/ipa/rpi/controller/rpi/tonemap.cpp\n> @@ -33,7 +33,7 @@ int Tonemap::read(const libcamera::YamlObject &params)\n>  \tconfig_.detailSlope = params[\"detail_slope\"].get<double>(0.1);\n>  \tconfig_.iirStrength = params[\"iir_strength\"].get<double>(1.0);\n>  \tconfig_.strength = params[\"strength\"].get<double>(1.0);\n> -\tconfig_.tonemap.read(params[\"tone_curve\"]);\n> +\tconfig_.tonemap.readYaml(params[\"tone_curve\"]);\n>  \treturn 0;\n>  }\n>  \n> diff --git a/src/ipa/rpi/controller/rpi/tonemap.h b/src/ipa/rpi/controller/rpi/tonemap.h\n> index f25aa47f..ba0cf5c4 100644\n> --- a/src/ipa/rpi/controller/rpi/tonemap.h\n> +++ b/src/ipa/rpi/controller/rpi/tonemap.h\n> @@ -6,8 +6,9 @@\n>   */\n>  #pragma once\n>  \n> +#include <libipa/pwl.h>\n> +\n>  #include \"algorithm.h\"\n> -#include \"pwl.h\"\n>  \n>  namespace RPiController {\n>  \n> @@ -16,7 +17,7 @@ struct TonemapConfig {\n>  \tdouble detailSlope;\n>  \tdouble iirStrength;\n>  \tdouble strength;\n> -\tPwl tonemap;\n> +\tlibcamera::ipa::Pwl tonemap;\n>  };\n>  \n>  class Tonemap : public Algorithm\n> diff --git a/src/ipa/rpi/controller/tonemap_status.h b/src/ipa/rpi/controller/tonemap_status.h\n> index 0e639946..e51a2b6c 100644\n> --- a/src/ipa/rpi/controller/tonemap_status.h\n> +++ b/src/ipa/rpi/controller/tonemap_status.h\n> @@ -6,12 +6,12 @@\n>   */\n>  #pragma once\n>  \n> -#include \"pwl.h\"\n> +#include <libipa/pwl.h>\n>  \n>  struct TonemapStatus {\n>  \tuint16_t detailConstant;\n>  \tdouble detailSlope;\n>  \tdouble iirStrength;\n>  \tdouble strength;\n> -\tRPiController::Pwl tonemap;\n> +\tlibcamera::ipa::Pwl tonemap;\n>  };\n> -- \n> 2.39.2\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 99EF0C3213\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 15 Apr 2024 12:52:36 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8013D63352;\n\tMon, 15 Apr 2024 14:52:35 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2235463339\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 15 Apr 2024 14:52:34 +0200 (CEST)","from ideasonboard.com (unknown\n\t[IPv6:2a00:6020:448c:6c00:7a0d:dd2e:881a:db83])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 6A5317E4;\n\tMon, 15 Apr 2024 14:51:48 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"ve5f1gGt\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1713185508;\n\tbh=w+9TkRnWwY7xgGwsG+Q0Qmw4R3wmryrnpsqKECGaJow=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=ve5f1gGtPMUW0LGeVKOCI+AQ5wIu/kBM5FT/ib2e0LWARmP5Jt4cePr9brrtMYhFr\n\t5KgQWhozPjeuSWRMghmi8juspJ81BthUkLmUQ9IGxW2D10hWquVCzY9riVbXFSBNgD\n\thzFl9lRR1AAXOYQeHVTeE8p9mm94M9RqQldtcIjA=","Date":"Mon, 15 Apr 2024 14:52:31 +0200","From":"Stefan Klug <stefan.klug@ideasonboard.com>","To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 4/4] ipa: rpi: controller: Use libipa's Pwl class","Message-ID":"<20240415125231.rpwuwobce7ws7cqa@jasper>","References":"<20240405080259.1806453-1-paul.elder@ideasonboard.com>\n\t<20240405080259.1806453-5-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20240405080259.1806453-5-paul.elder@ideasonboard.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]