{"id":27066,"url":"https://patchwork.libcamera.org/api/patches/27066/?format=json","web_url":"https://patchwork.libcamera.org/patch/27066/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/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":"<20260626-ipu3-libipa-rework-v2-6-41546e23de3e@ideasonboard.com>","date":"2026-06-26T13:05:53","name":"[v2,06/12] ipa: libipa: Add GammaAlgorithm class","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"c2bbb8d8495d885647012566814542f387d54ec1","submitter":{"id":156,"url":"https://patchwork.libcamera.org/api/people/156/?format=json","name":"Dan Scally","email":"dan.scally@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/27066/mbox/","series":[{"id":6022,"url":"https://patchwork.libcamera.org/api/series/6022/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=6022","date":"2026-06-26T13:05:47","name":"libipa: Re-work IPU3 IPA to use libipa algorithms","version":2,"mbox":"https://patchwork.libcamera.org/series/6022/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/27066/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/27066/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 1DAC9C3305\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 26 Jun 2026 13:06:21 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 3818565EE1;\n\tFri, 26 Jun 2026 15:06:15 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5F0CB65EE8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 26 Jun 2026 15:06:06 +0200 (CEST)","from [127.0.1.1]\n\t(chfd-03-b2-v4wan-176392-cust229.vm15.cable.virginm.net\n\t[82.19.20.230])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 7A76519EC;\n\tFri, 26 Jun 2026 15:05:25 +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=\"fTDSjas1\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1782479125;\n\tbh=3GaMPMabKUaQu1vwLcblL1vRtBOXJ1YreK2a2eNpgww=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n\tb=fTDSjas1W2FWYmrURHuNjES8eYijbr/R3p8EJaO50drGhmF59lPbgGhzxW2NnJV7y\n\txtYrFK4IA8S/fYQkO205ETwcQHBxsV3THXnl+OoXkaHFRZwva63mLwxtYJwR7NLA+P\n\tCfRXjXnBTFiLeuKVaS4RAV1qgpWgEPIosKg5QGw0=","From":"Daniel Scally <dan.scally@ideasonboard.com>","Date":"Fri, 26 Jun 2026 14:05:53 +0100","Subject":"[PATCH v2 06/12] ipa: libipa: Add GammaAlgorithm class","MIME-Version":"1.0","Content-Type":"text/plain; charset=\"utf-8\"","Content-Transfer-Encoding":"7bit","Message-Id":"<20260626-ipu3-libipa-rework-v2-6-41546e23de3e@ideasonboard.com>","References":"<20260626-ipu3-libipa-rework-v2-0-41546e23de3e@ideasonboard.com>","In-Reply-To":"<20260626-ipu3-libipa-rework-v2-0-41546e23de3e@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Daniel Scally <dan.scally@ideasonboard.com>, \n\tJacopo Mondi <jacopo.mondi@ideasonboard.com>","X-Mailer":"b4 0.14.2","X-Developer-Signature":"v=1; a=openpgp-sha256; l=12001;\n\ti=dan.scally@ideasonboard.com; h=from:subject:message-id;\n\tbh=3GaMPMabKUaQu1vwLcblL1vRtBOXJ1YreK2a2eNpgww=;\n\tb=owEBbQKS/ZANAwAKAchJV3psRXUyAcsmYgBqPnk6AhIs9ULOwxe7qtF6jSqrqc7qmGLsOsKPy\n\tPw4JQz6qr6JAjMEAAEKAB0WIQQqyuwyDnZdb+mxmm/ISVd6bEV1MgUCaj55OgAKCRDISVd6bEV1\n\tMtZxD/9F/pQ1pxLysCUeRn3vcBGL/dyNXV2tk/N8+nUp9f7R67GPzmK2DSKYlGKfhq8R1HDPcuz\n\tS4KOOsRw+fGCUsBY3fgZ8+RJ0x5QG0pI6NkMzYdKKOvwZVveCq/drIOYyff0WIt4cVPZssrKru3\n\tOoODqSoMTco1nS+3cti2mGFQvG78EmQ+X3t+Jm49M9XjT0qJ46hN9V2+cxG80Y1LA5qWJuh2fA1\n\tfpZ0bmucEJJR6+bfYvv0gSOXAPDhsDqofO69Hy60znsq+liNJNTmUlnPuS7bZ6YTQd41wdXlU3a\n\tGB1T9k7MfkITXpI9UUTWXwgH7Vxj4IFLE+lEtKs7dmylw+l6mWicOLmq6tcpTXiTFbaKxTv7qiY\n\tp+yoYUPzxZBQTQF8EDeKspLjk/22o8tisBsnm+rgR1MCexcXhcGO2R5JRJkR4zYKBqya76ftaCf\n\tMpcaRNmxujf+OlCZBfsux22HaadMG46wYubXwVSpzPyTPJ55uM2UnFnh41AFYJceyIX3wqH7iFr\n\tygtQLDUS+ioccWqCOi9b6QWxv8qnBvyRtF7xG9q6fFDDQBGX1Zbm0tLRwgEuYAFXjLQKrY/w0zn\n\tM1qw+mI8VNVaZPdm85FytABuyV1WRzNwlPUziNGL5MgIebTbPPRUeastSky392KL7V03H8hG9gV\n\tcsBvNPwRB+jxTgg==","X-Developer-Key":"i=dan.scally@ideasonboard.com; a=openpgp;\n\tfpr=EEC699ACA1B7CB5D31330C0BBD501C2A3546CCF6","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":"Add a base GammaAlgorithm class that can be used by IPA specific\ngamma algorithms to reduce the amount of work that they need to\nimplement.\n\nReviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\nSigned-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n---\nChanges in v2:\n\n        - Updated documentation comments, and minor styling changes\n---\n src/ipa/libipa/gamma.cpp   | 257 +++++++++++++++++++++++++++++++++++++++++++++\n src/ipa/libipa/gamma.h     |  94 +++++++++++++++++\n src/ipa/libipa/meson.build |   2 +\n 3 files changed, 353 insertions(+)","diff":"diff --git a/src/ipa/libipa/gamma.cpp b/src/ipa/libipa/gamma.cpp\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..5fec5329a50f0b3dd0e39504dfbb63484ca4fcd9\n--- /dev/null\n+++ b/src/ipa/libipa/gamma.cpp\n@@ -0,0 +1,257 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026 Ideas on Board Oy\n+ *\n+ * libIPA Gamma correction algorithm\n+ */\n+\n+#include \"gamma.h\"\n+\n+#include <numeric>\n+\n+#include <libcamera/controls.h>\n+\n+#include \"libcamera/internal/value_node.h\"\n+\n+/**\n+ * \\file gamma.h\n+ * \\brief libipa implementation of a gamma curve correction algorithm\n+ */\n+\n+namespace libcamera {\n+\n+namespace ipa {\n+\n+LOG_DEFINE_CATEGORY(Gamma)\n+\n+namespace gamma {\n+\n+/**\n+ * \\struct ActiveState\n+ * \\brief Active gamma correction algorithm state\n+ *\n+ * \\var ActiveState::gamma\n+ * \\brief The gamma correction value\n+ */\n+\n+/**\n+ * \\struct FrameContext\n+ * \\brief Per-frame gamma correction settings\n+ *\n+ * \\var FrameContext::gamma\n+ * \\brief The gamma correction value applied for this frame\n+ *\n+ * \\var FrameContext::update\n+ * \\brief A flag instructing the algorithm to push an update to the hardware\n+ */\n+\n+} /* namespace gamma */\n+\n+/**\n+ * \\brief The default gamma correction value\n+ */\n+const float kDefaultGamma = 2.2f;\n+\n+/**\n+ * \\class GammaAlgorithmBase\n+ * \\brief Base class for GammaAlgorithm to implement non-templated functions\n+ *\n+ * This base class for GammeaAlgorithm allows us to implement non templated\n+ * functions. IPA specific implementations shall derive from GammaAlgorithm and\n+ * not this class.\n+ */\n+\n+/**\n+ * \\fn GammaAlgorithmBase::GammaAlgorithmBase\n+ * \\brief Construct an instance of the class\n+ * \\param[in] nLutNodes Set the number of function knee-points expected by the\n+ * IPA algorithm\n+ */\n+\n+/**\n+ * \\brief Initialise the algorithm with the given tuning data\n+ * \\param[out] controls The ControlList into which this algorithm's supported\n+ * controls will be emplaced.\n+ * \\param[in] tuningData The tuning data to use with the algorithm\n+ * \\param[in] segments A vector of segment spacings to define a custom\n+ * X coordinate system for the curve\n+ *\n+ * Parse \\a tuningData and \\a segments to initialize the gamma correction curve.\n+ * The tuning data may contain a default gamma value to use; otherwise the value\n+ * of \\a kDefaultGamma will be taken as the default. The piecewise linear\n+ * function will be applied on a number of knots whose position is described by\n+ * the optional \\a segments argument, which describes each segment's relative\n+ * length.\n+ *\n+ * For example, if the gamma correction has to be applied on 16 equally spaced\n+ * sampling points, a \\segments array like:\n+ *\n+ * [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n+ *\n+ * would result in evenly spaced knee-points along the X-axis.\n+ *\n+ * Hardware may expect the knee-points to be spaced more densely towards the\n+ * start of the curve and more sparsely towards the end, in which case an\n+ * alternative array might be:\n+ *\n+ * [1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 8, 8, 8, 8, 8]\n+ *\n+ * As the values in \\a segments represent the distance between two knee-points\n+ * relative to the total distance between the first and last point, the length\n+ * of \\a segments should be equal to the number of knee-points minus one.\n+ *;\n+ * If an IPA implementation doesnt't provide \\a segments, the GammaAlgorithm\n+ * class consturcts an evenly-spaced default.\n+ *\n+ * IPA modules are expected to call this function as part of their\n+ * implementation of Algorithm::init()\n+ *\n+ * @return 0 on success, a negative error code otherwise\n+ */\n+int GammaAlgorithmBase::init(ControlInfoMap::Map &controls, const ValueNode &tuningData,\n+\t\t\t     Span<unsigned int> segments)\n+{\n+\t/*\n+\t * If the caller doesn't pass in a segment list we simply construct one\n+\t * with equally spaced segments. We need one less segment than we have\n+\t * LUT nodes.\n+\t */\n+\tunsigned int expectedNSegments = nLutNodes_ - 1;\n+\n+\tif (segments.empty()) {\n+\t\tfor (unsigned int i = 0; i < expectedNSegments; i++)\n+\t\t\tsegments_.push_back(1);\n+\t} else {\n+\t\tsegments_.assign(segments.begin(), segments.end());\n+\t}\n+\n+\tif (segments_.size() != expectedNSegments)\n+\t\treturn -EINVAL;\n+\n+\tsegmentsSum_ = std::accumulate(segments_.begin(), segments_.end(), 0.0f);\n+\n+\tdefaultGamma_ = tuningData[\"gamma\"].get<double>(kDefaultGamma);\n+\tcontrols[&controls::Gamma] = ControlInfo(0.1f, 10.0f, defaultGamma_);\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * \\brief Configure the gamma correction algorithm\n+ * \\param[out] state The gamma correction algorithm's active state\n+ *\n+ * Reset to the default gamma correction value.\n+ *\n+ * IPA modules are expected to call this function as part of their\n+ * implementation of Algorithm::configure()\n+ */\n+void GammaAlgorithmBase::configure(gamma::ActiveState &state)\n+{\n+\tstate.gamma = defaultGamma_;\n+}\n+\n+/**\n+ * \\brief Queue a request to the gamma correction algorithm\n+ * \\param[in] state The algorithm's active state\n+ * \\param[in] frame The current frame number\n+ * \\param[in] context The algorithm's frame context\n+ * \\param[in] controls The ControlList that was queued with the request\n+ *\n+ * Queue a new request to the gamma correction algorithm and handle any relevant\n+ * controls that were queued. The only control currently handled is:\n+ *\n+ * - controls::Gamma\n+ *\n+ * If a control with that ID is queued the value is stored in \\a state and\n+ * \\a context.\n+ *\n+ * IPA modules are expected to call this function as part of their\n+ * implementation of Algorithm::queueRequest()\n+ */\n+void GammaAlgorithmBase::queueRequest(gamma::ActiveState &state,\n+\t\t\t\t      const uint32_t frame,\n+\t\t\t\t      gamma::FrameContext &context,\n+\t\t\t\t      const ControlList &controls)\n+{\n+\tif (frame == 0)\n+\t\tcontext.update = true;\n+\n+\tconst auto &gamma = controls.get(controls::Gamma);\n+\tif (gamma) {\n+\t\tstate.gamma = *gamma;\n+\t\tcontext.update = true;\n+\t\tLOG(Gamma, Info) << \"Set gamma to \" << *gamma;\n+\t}\n+\n+\tcontext.gamma = state.gamma;\n+}\n+\n+/**\n+ * \\brief Populate metadata with the gamma correction values for a frame\n+ * \\param[in] context The frame context\n+ * \\param[out] metadata The ControlList of metadata for a frame\n+ *\n+ * Report the gamma value used to calculate the correction curve that was\n+ * applied to a frame.\n+ */\n+void GammaAlgorithmBase::process(gamma::FrameContext &context, ControlList &metadata)\n+{\n+\tmetadata.set(controls::Gamma, context.gamma);\n+}\n+\n+/**\n+ * \\var GammaAlgorithmBase::nLutNodes_\n+ * \\brief The number of gamma LUT sampling points\n+ */\n+\n+/**\n+ * \\var GammaAlgorithmBase::defaultGamma_\n+ * \\brief The default gamma parameter\n+ */\n+\n+/**\n+ * \\var GammaAlgorithmBase::segments_\n+ * \\brief The vector of segment sizes describing the space between knee-points\n+ */\n+\n+/**\n+ * \\var GammaAlgorithmBase::segmentsSum_\n+ * \\brief The sum of \\a GammaAlgorithmBase::segments_\n+ */\n+\n+/**\n+ * \\class GammaAlgorithm\n+ * \\brief The libipa gamma correction algorithm\n+ * \\tparam nLutNodes The number of gamma LUT sampling points\n+ * \\tparam UQ The fixedpoint representation of the gamma correction values\n+ *\n+ * Gamma correction adjusts for the differences in the way light is perceived\n+ * by a camera and the human eye by applying a function to the input values.\n+ * The GammaAlgorithm class facilitates this by building a piecewise linear\n+ * function from a gamma parameter and supplying it in the hardware-specific\n+ * formats defined by the IPA algorithms.\n+ *\n+ * IPA modules are expected to store an instance of GammaAlgorithm as a class\n+ * member, templated with the format and number of knee-points in the PWL\n+ * expected by their hardware and then call its functions in their overload of\n+ * the Algorithm class's function.\n+ *\n+ * When an application queues a new value for the gamma parameter with a\n+ * Request, the GammaAlgorithm will recalculate and populate the new LUT to be\n+ * sent to the ISP.\n+ *\n+ * Useful links:\n+ * - https://www.cambridgeincolour.com/tutorials/gamma-correction.htm\n+ * - https://en.wikipedia.org/wiki/SRGB\n+ */\n+\n+/**\n+ * \\fn GammaAlgorithm::prepare()\n+ * \\tparam T The type of data expected by the hardware's look-up table\n+ * \\param[in] context The frame context\n+ * \\param[out] lut The Span into which to place the calculated look-up table\n+ */\n+\n+} /* namespace ipa */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/libipa/gamma.h b/src/ipa/libipa/gamma.h\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..94be8c7a2328e7b5a3618b0d65989ee511cece09\n--- /dev/null\n+++ b/src/ipa/libipa/gamma.h\n@@ -0,0 +1,94 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026 Ideas on Board Oy\n+ *\n+ * libIPA Gamma correction algorithm\n+ */\n+\n+#pragma once\n+\n+#include <cmath>\n+#include <vector>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/span.h>\n+\n+#include <libcamera/control_ids.h>\n+\n+#include \"libcamera/internal/value_node.h\"\n+\n+#include \"fixedpoint.h\"\n+\n+namespace libcamera {\n+\n+namespace ipa {\n+\n+LOG_DECLARE_CATEGORY(Gamma)\n+\n+namespace gamma {\n+\n+struct ActiveState {\n+\tdouble gamma;\n+};\n+\n+struct FrameContext {\n+\tdouble gamma;\n+\tbool update;\n+};\n+\n+} /* namespace gamma */\n+\n+class GammaAlgorithmBase\n+{\n+public:\n+\tGammaAlgorithmBase(unsigned int nLutNodes)\n+\t\t: nLutNodes_(nLutNodes)\n+\t{\n+\t}\n+\n+\tint init(ControlInfoMap::Map &controls, const ValueNode &tuningData,\n+\t\t Span<unsigned int> segments = {});\n+\n+\tvoid configure(gamma::ActiveState &state);\n+\tvoid queueRequest(gamma::ActiveState &state, const uint32_t frame,\n+\t\t\t  gamma::FrameContext &context, const ControlList &controls);\n+\tvoid process(gamma::FrameContext &context, ControlList &metadata);\n+\n+protected:\n+\tunsigned int nLutNodes_;\n+\tfloat defaultGamma_;\n+\tstd::vector<unsigned int> segments_;\n+\tunsigned int segmentsSum_;\n+};\n+\n+template<unsigned int nLutNodes, typename UQ>\n+class GammaAlgorithm : public GammaAlgorithmBase\n+{\n+public:\n+\tGammaAlgorithm()\n+\t\t: GammaAlgorithmBase(nLutNodes)\n+\t{\n+\t}\n+\n+\ttemplate<typename T>\n+\tvoid prepare(gamma::FrameContext &context, Span<T> lut)\n+\t{\n+\t\tfloat x = 0;\n+\n+\t\tfor (unsigned int i = 0; i < nLutNodes_; i++) {\n+\t\t\tfloat gamma = std::pow(x / segmentsSum_,\n+\t\t\t\t\t       1.0 / context.gamma);\n+\t\t\tlut[i] = UQ(gamma).quantized();\n+\n+\t\t\tLOG(Gamma, Debug) << \"LUT[\" << i << \"]=\" << gamma\n+\t\t\t\t\t  << \"(\" << lut[i] << \")\";\n+\n+\t\t\tif (i < segments_.size())\n+\t\t\t\tx += segments_[i];\n+\t\t}\n+\t}\n+};\n+\n+} /* namespace ipa */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\nindex da6ea0c5e13000f78b2196c7334610c350f1ad13..565da9be9059f2167d107061d643e58202e655ef 100644\n--- a/src/ipa/libipa/meson.build\n+++ b/src/ipa/libipa/meson.build\n@@ -12,6 +12,7 @@ libipa_headers = files([\n     'exposure_mode_helper.h',\n     'fc_queue.h',\n     'fixedpoint.h',\n+    'gamma.h',\n     'histogram.h',\n     'interpolator.h',\n     'lsc.h',\n@@ -37,6 +38,7 @@ libipa_sources = files([\n     'exposure_mode_helper.cpp',\n     'fc_queue.cpp',\n     'fixedpoint.cpp',\n+    'gamma.cpp',\n     'histogram.cpp',\n     'interpolator.cpp',\n     'lsc.cpp',\n","prefixes":["v2","06/12"]}