{"id":19845,"url":"https://patchwork.libcamera.org/api/1.1/patches/19845/?format=json","web_url":"https://patchwork.libcamera.org/patch/19845/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20240405080259.1806453-5-paul.elder@ideasonboard.com>","date":"2024-04-05T08:02:59","name":"[4/4] ipa: rpi: controller: Use libipa's Pwl class","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"a111226b87834ddaeae2ca91b323c9d918d56d00","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/1.1/people/17/?format=json","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/19845/mbox/","series":[{"id":4249,"url":"https://patchwork.libcamera.org/api/1.1/series/4249/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=4249","date":"2024-04-05T08:02:55","name":"ipa: Move Pwl from Raspberry Pi to libipa","version":1,"mbox":"https://patchwork.libcamera.org/series/4249/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/19845/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/19845/checks/","tags":{},"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 B9469C0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  5 Apr 2024 08:03:23 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5F64F61C2B;\n\tFri,  5 Apr 2024 10:03:23 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 53D2D61C24\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  5 Apr 2024 10:03:19 +0200 (CEST)","from pyrite.hamster-moth.ts.net (h175-177-049-156.catv02.itscom.jp\n\t[175.177.49.156])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id A37A763B;\n\tFri,  5 Apr 2024 10:02:39 +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=\"btd1LuvC\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1712304160;\n\tbh=Z4HqAUvVy029kHbPsfl+W5PVBz9y5SD3tGAOb6qsaeo=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=btd1LuvCkJFb2lAsMrZd4rlpJzBuMlGEwxsRQNEjZF8am2aWNaadM7UDjX3eeo8ud\n\tu9a0eyUAvIYukPZ9vXF2ulm285pUpHVsQ17OGVS0Mk9B29q/ZgKfIpd/B2gGIRPubf\n\tXjoPhT3jd4BJuMuQezMSU9KX1wTR+O0RFWHtcOy4=","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Paul Elder <paul.elder@ideasonboard.com>","Subject":"[PATCH 4/4] ipa: rpi: controller: Use libipa's Pwl class","Date":"Fri,  5 Apr 2024 17:02:59 +0900","Message-Id":"<20240405080259.1806453-5-paul.elder@ideasonboard.com>","X-Mailer":"git-send-email 2.39.2","In-Reply-To":"<20240405080259.1806453-1-paul.elder@ideasonboard.com>","References":"<20240405080259.1806453-1-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","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>"},"content":"To reduce code duplication, use the Pwl class from libipa. This also\nremoves the Pwl class from the Raspberry Pi IPA.\n\nSigned-off-by: Paul Elder <paul.elder@ideasonboard.com>\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","diff":"diff --git a/src/ipa/rpi/controller/cac_status.h b/src/ipa/rpi/controller/cac_status.h\nindex 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;\ndiff --git a/src/ipa/rpi/controller/contrast_status.h b/src/ipa/rpi/controller/contrast_status.h\nindex 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 };\ndiff --git a/src/ipa/rpi/controller/meson.build b/src/ipa/rpi/controller/meson.build\nindex 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)\ndiff --git a/src/ipa/rpi/controller/pwl.cpp b/src/ipa/rpi/controller/pwl.cpp\ndeleted file mode 100644\nindex 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-}\ndiff --git a/src/ipa/rpi/controller/pwl.h b/src/ipa/rpi/controller/pwl.h\ndeleted file mode 100644\nindex 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 */\ndiff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp\nindex 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}\ndiff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h\nindex 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);\ndiff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp\nindex 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/*\ndiff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h\nindex 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;\ndiff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp\nindex 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 << \")\";\ndiff --git a/src/ipa/rpi/controller/rpi/awb.h b/src/ipa/rpi/controller/rpi/awb.h\nindex 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 */\ndiff --git a/src/ipa/rpi/controller/rpi/ccm.cpp b/src/ipa/rpi/controller/rpi/ccm.cpp\nindex 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++)\ndiff --git a/src/ipa/rpi/controller/rpi/ccm.h b/src/ipa/rpi/controller/rpi/ccm.h\nindex 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\ndiff --git a/src/ipa/rpi/controller/rpi/contrast.cpp b/src/ipa/rpi/controller/rpi/contrast.cpp\nindex 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);\ndiff --git a/src/ipa/rpi/controller/rpi/contrast.h b/src/ipa/rpi/controller/rpi/contrast.h\nindex 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\ndiff --git a/src/ipa/rpi/controller/rpi/geq.cpp b/src/ipa/rpi/controller/rpi/geq.cpp\nindex 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;\ndiff --git a/src/ipa/rpi/controller/rpi/geq.h b/src/ipa/rpi/controller/rpi/geq.h\nindex 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\ndiff --git a/src/ipa/rpi/controller/rpi/hdr.cpp b/src/ipa/rpi/controller/rpi/hdr.cpp\nindex 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. */\ndiff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h\nindex 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];\ndiff --git a/src/ipa/rpi/controller/rpi/tonemap.cpp b/src/ipa/rpi/controller/rpi/tonemap.cpp\nindex 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 \ndiff --git a/src/ipa/rpi/controller/rpi/tonemap.h b/src/ipa/rpi/controller/rpi/tonemap.h\nindex 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\ndiff --git a/src/ipa/rpi/controller/tonemap_status.h b/src/ipa/rpi/controller/tonemap_status.h\nindex 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","prefixes":["4/4"]}