{"id":27486,"url":"https://patchwork.libcamera.org/api/patches/27486/?format=json","web_url":"https://patchwork.libcamera.org/patch/27486/","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":"<20260723154327.1357866-38-barnabas.pocze@ideasonboard.com>","date":"2026-07-23T15:43:20","name":"[RFC,v2,37/43] ipa: simple: agc: Move to libipa","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"925738020442f41f5f43333a506e5ac075f37253","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/?format=json","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/27486/mbox/","series":[{"id":6075,"url":"https://patchwork.libcamera.org/api/series/6075/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=6075","date":"2026-07-23T15:42:43","name":"ipa: libipa: agc rework","version":2,"mbox":"https://patchwork.libcamera.org/series/6075/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/27486/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/27486/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 AF3B5C32FE\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 23 Jul 2026 15:44:23 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4B82167F32;\n\tThu, 23 Jul 2026 17:44: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 E5FE667ECA\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 23 Jul 2026 17:43:39 +0200 (CEST)","from pb-laptop.local (185.182.215.156.nat.pool.zt.hu\n\t[185.182.215.156])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 06D2423B9\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 23 Jul 2026 17:42:38 +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=\"PDO9VAmL\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1784821359;\n\tbh=lNNzOgGhrkEv51Bw/xKoFPbnCAEmMF1Eb6AmodeUOVM=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=PDO9VAmLRyKuu3CYTjo3oGJd68m67edmm6ic+4ddPKCC4BijXppfMETGXzgEGkKev\n\thec6GyPOy7vWOzKo37PCO1FAw3y8zKI7jD/50Q/Awu4zBdy7dx7LpEX+y6HixpByln\n\tTtUNeq46Bq1m8Berkv/IBsFR++n3rvWDZgW7m0fY=","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Subject":"[RFC PATCH v2 37/43] ipa: simple: agc: Move to libipa","Date":"Thu, 23 Jul 2026 17:43:20 +0200","Message-ID":"<20260723154327.1357866-38-barnabas.pocze@ideasonboard.com>","X-Mailer":"git-send-email 2.55.0","In-Reply-To":"<20260723154327.1357866-1-barnabas.pocze@ideasonboard.com>","References":"<20260723154327.1357866-1-barnabas.pocze@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","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":"Move the agc algorithm used into a separate component in libipa, and use it.\nWhile it is also a (mean) luminance based algorithm, there is already\n`AgcMeanLuminance`, so call it `AgcMSV` (from \"mean sample value\").\n\nThis move also removes the dependency on the black level and makes it\nuse the `Histogram` type instead of the software isp specific types.\nWith the removal of the black level information, it is assumed to be 0\nand a black level corrected histogram is expected.\n\nSigned-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n---\n src/ipa/libipa/agc_msv.cpp        | 218 ++++++++++++++++++++++++++++++\n src/ipa/libipa/agc_msv.h          |  51 +++++++\n src/ipa/libipa/meson.build        |   2 +\n src/ipa/simple/algorithms/agc.cpp | 167 ++++-------------------\n src/ipa/simple/algorithms/agc.h   |   7 +-\n 5 files changed, 300 insertions(+), 145 deletions(-)\n create mode 100644 src/ipa/libipa/agc_msv.cpp\n create mode 100644 src/ipa/libipa/agc_msv.h","diff":"diff --git a/src/ipa/libipa/agc_msv.cpp b/src/ipa/libipa/agc_msv.cpp\nnew file mode 100644\nindex 0000000000..63580a0055\n--- /dev/null\n+++ b/src/ipa/libipa/agc_msv.cpp\n@@ -0,0 +1,218 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Red Hat Inc.\n+ *\n+ * Luminance mean sample value based AGC algorithm\n+ */\n+\n+#include \"agc_msv.h\"\n+\n+#include <algorithm>\n+#include <cmath>\n+#include <optional>\n+\n+#include <libcamera/base/log.h>\n+\n+#include \"histogram.h\"\n+\n+namespace libcamera {\n+\n+namespace ipa {\n+\n+LOG_DEFINE_CATEGORY(AgcMSV)\n+\n+/**\n+ * \\class AgcMSV\n+ * \\brief Binned mean luminance based AGC algorithm\n+ */\n+\n+/**\n+ * \\class AgcMSV::Limits\n+ * \\brief Set of limits for the algorithm\n+ *\n+ * \\var AgcMSV::Limits::exposure\n+ * \\brief Minimum and maximum allowed exposure (in lines)\n+ *\n+ * \\var AgcMSV::Limits::gain\n+ * \\brief Minimum and maximum allowed gain\n+ *\n+ * \\var AgcMSV::Limits::gainMinStep\n+ * \\brief Minimum allowed gain adjustment\n+ *\n+ * \\var AgcMSV::Limits::gain1\n+ * \\brief The gain value assumed to result in a gain of 1.0\n+ *\n+ * The algorithm will not lower the gain value below this.\n+ */\n+\n+/**\n+ * \\struct AgcMSV::Params\n+ * \\brief Collection of parameters for the algorithm\n+ *\n+ * \\var AgcMSV::Params::yHist\n+ * \\brief Luminance histogram of the frame\n+ *\n+ * \\var AgcMSV::Params::exposure\n+ * \\brief Effective exposure of the frame\n+ *\n+ * \\var AgcMSV::Params::gain\n+ * \\brief Effective gain of the frame\n+ */\n+\n+/**\n+ * \\struct AgcMSV::Result\n+ * \\brief Collection of results of the algorithm\n+ *\n+ * \\var AgcMSV::Result::exposure\n+ * \\brief The applicable exposure (in lines)\n+ *\n+ * \\var AgcMSV::Result::analogueGain\n+ * \\brief The applicable analogue gain\n+ */\n+\n+namespace {\n+\n+/*\n+ * The number of bins to use for the optimal exposure calculations.\n+ */\n+static constexpr unsigned int kExposureBinsCount = 5;\n+\n+/*\n+ * The exposure is optimal when the mean sample value of the histogram is\n+ * in the middle of the range.\n+ */\n+static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;\n+\n+/*\n+ * This implements the hysteresis for the exposure adjustment.\n+ * It is small enough to have the exposure close to the optimal, and is big\n+ * enough to prevent the exposure from wobbling around the optimal value.\n+ */\n+static constexpr float kExposureSatisfactory = 0.2;\n+\n+/*\n+ * Proportional gain for exposure/gain adjustment. Maps the MSV error to a\n+ * multiplicative correction factor:\n+ *\n+ *   factor = 1.0 + kExpProportionalGain * error\n+ *\n+ * With kExpProportionalGain = 0.04:\n+ *   - max error ~2.5 -> factor 1.10 (~10% step, same as before)\n+ *   - error 1.0      -> factor 1.04 (~4% step)\n+ *   - error 0.3      -> factor 1.012 (~1.2% step)\n+ *\n+ * This replaces the fixed 10% bang-bang step with a proportional correction\n+ * that converges smoothly and avoids overshooting near the target.\n+ */\n+static constexpr float kExpProportionalGain = 0.04;\n+\n+/*\n+ * Maximum multiplicative step per frame, to bound the correction when the\n+ * scene changes dramatically.\n+ */\n+static constexpr float kExpMaxStep = 0.15;\n+\n+std::optional<float> calculateMSV(const Histogram &histogram)\n+{\n+\t/*\n+\t * Calculate Mean Sample Value (MSV) according to formula from:\n+\t * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf\n+\t */\n+\tconst unsigned int yHistValsPerBin = histogram.bins() / kExposureBinsCount;\n+\tconst unsigned int yHistValsPerBinMod =\n+\t\thistogram.bins() / (histogram.bins() % kExposureBinsCount + 1);\n+\tint exposureBins[kExposureBinsCount] = {};\n+\tunsigned int denom = 0;\n+\tunsigned int num = 0;\n+\n+\tif (yHistValsPerBin == 0)\n+\t\treturn {};\n+\n+\tfor (unsigned int i = 0; i < histogram.bins(); i++) {\n+\t\tunsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;\n+\t\texposureBins[idx] += histogram[i];\n+\t}\n+\n+\tfor (unsigned int i = 0; i < kExposureBinsCount; i++) {\n+\t\tLOG(AgcMSV, Debug) << i << \": \" << exposureBins[i];\n+\t\tdenom += exposureBins[i];\n+\t\tnum += exposureBins[i] * (i + 1);\n+\t}\n+\n+\treturn (denom == 0 ? 0 : static_cast<float>(num) / denom);\n+}\n+\n+} /* namespace */\n+\n+/**\n+ * \\brief Set the limits for the algorithm\n+ */\n+void AgcMSV::setLimits(const Limits &limits)\n+{\n+\tlimits_ = limits;\n+}\n+\n+/**\n+ * \\brief Calculate a new set of AGC parameters\n+ */\n+AgcMSV::Result AgcMSV::calculateNewEv(const Params &params)\n+{\n+\tauto exposureMSV = calculateMSV(params.yHist);\n+\tif (!exposureMSV) {\n+\t\tLOG(AgcMSV, Debug)\n+\t\t\t<< \"Not adjusting exposure due to insufficient histogram data\";\n+\t\treturn { params.exposure, params.gain };\n+\t}\n+\n+\treturn updateExposure(params.exposure, params.gain, *exposureMSV);\n+}\n+\n+AgcMSV::Result AgcMSV::updateExposure(uint32_t exposure, double again, float exposureMSV)\n+{\n+\tfloat error = kExposureOptimal - exposureMSV;\n+\tif (std::abs(error) <= kExposureSatisfactory)\n+\t\treturn { exposure, again };\n+\n+\t/*\n+\t * Compute a proportional correction factor. The sign of the error\n+\t * determines the direction: positive error means too dark (increase),\n+\t * negative means too bright (decrease).\n+\t */\n+\tfloat step = std::clamp(error * kExpProportionalGain,\n+\t\t\t\t-kExpMaxStep, kExpMaxStep);\n+\tfloat factor = 1.0f + step;\n+\n+\tif (factor > 1.0f) {\n+\t\t/* Scene too dark: increase exposure first, then gain. */\n+\t\tif (exposure < limits_.exposure[1]) {\n+\t\t\tuint32_t next = exposure * factor;\n+\t\t\texposure = std::max(next, exposure + 1);\n+\t\t} else {\n+\t\t\tdouble next = again * factor;\n+\t\t\tagain = std::max(next, again + limits_.gainMinStep);\n+\t\t}\n+\t} else {\n+\t\t/* Scene too bright: decrease gain first, then exposure. */\n+\t\tif (again > limits_.gain1) {\n+\t\t\tdouble next = again * factor;\n+\t\t\tagain = std::min(next, again - limits_.gainMinStep);\n+\t\t} else {\n+\t\t\tuint32_t next = exposure * factor;\n+\t\t\texposure = std::min(next, exposure - 1);\n+\t\t}\n+\t}\n+\n+\texposure = std::clamp(exposure, limits_.exposure[0], limits_.exposure[1]);\n+\tagain = std::clamp(again, limits_.gain[0], limits_.gain[1]);\n+\n+\tLOG(AgcMSV, Debug)\n+\t\t<< \"exposureMSV:\" << exposureMSV\n+\t\t<< \" error:\" << error << \" factor:\" << factor\n+\t\t<< \" exposure:\" << exposure << \" analogue-gain:\" << again;\n+\n+\treturn { exposure, again };\n+}\n+\n+} /* namespace ipa */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/libipa/agc_msv.h b/src/ipa/libipa/agc_msv.h\nnew file mode 100644\nindex 0000000000..13e67fd57c\n--- /dev/null\n+++ b/src/ipa/libipa/agc_msv.h\n@@ -0,0 +1,51 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Red Hat Inc.\n+ *\n+ * Luminance mean sample value based AGC algorithm\n+ */\n+\n+#pragma once\n+\n+#include \"histogram.h\"\n+\n+#include <libcamera/base/utils.h>\n+\n+namespace libcamera {\n+\n+namespace ipa {\n+\n+class AgcMSV\n+{\n+public:\n+\tstruct Limits {\n+\t\tstd::array<uint32_t, 2> exposure;\n+\t\tstd::array<double, 2> gain;\n+\t\tdouble gainMinStep;\n+\t\tdouble gain1;\n+\t};\n+\n+\tstruct Params {\n+\t\tconst Histogram &yHist;\n+\t\tuint32_t exposure;\n+\t\tdouble gain;\n+\t};\n+\n+\tstruct Result {\n+\t\tuint32_t exposure;\n+\t\tdouble analogueGain;\n+\t};\n+\n+\tvoid setLimits(const Limits &limits);\n+\t[[nodiscard]] Result calculateNewEv(const Params &params);\n+\n+private:\n+\tAgcMSV::Result updateExposure(uint32_t exposure, double again, float exposureMSV);\n+\n+\tLimits limits_ = {};\n+};\n+\n+\n+} /* namespace ipa */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\nindex ca681fa5af..8896eb8f89 100644\n--- a/src/ipa/libipa/meson.build\n+++ b/src/ipa/libipa/meson.build\n@@ -3,6 +3,7 @@\n libipa_headers = files([\n     'agc.h',\n     'agc_mean_luminance.h',\n+    'agc_msv.h',\n     'algorithm.h',\n     'awb_bayes.h',\n     'awb_grey.h',\n@@ -25,6 +26,7 @@ libipa_headers = files([\n libipa_sources = files([\n     'agc.cpp',\n     'agc_mean_luminance.cpp',\n+    'agc_msv.cpp',\n     'algorithm.cpp',\n     'awb_bayes.cpp',\n     'awb_grey.cpp',\ndiff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp\nindex 199b444f5a..7b980c7382 100644\n--- a/src/ipa/simple/algorithms/agc.cpp\n+++ b/src/ipa/simple/algorithms/agc.cpp\n@@ -7,10 +7,6 @@\n \n #include \"agc.h\"\n \n-#include <algorithm>\n-#include <cmath>\n-#include <optional>\n-#include <stdint.h>\n #include <utility>\n \n #include <libcamera/base/log.h>\n@@ -25,138 +21,22 @@ LOG_DEFINE_CATEGORY(IPASoftExposure)\n \n namespace ipa::soft::algorithms {\n \n-/*\n- * The number of bins to use for the optimal exposure calculations.\n- */\n-static constexpr unsigned int kExposureBinsCount = 5;\n-\n-/*\n- * The exposure is optimal when the mean sample value of the histogram is\n- * in the middle of the range.\n- */\n-static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;\n-\n-/*\n- * This implements the hysteresis for the exposure adjustment.\n- * It is small enough to have the exposure close to the optimal, and is big\n- * enough to prevent the exposure from wobbling around the optimal value.\n- */\n-static constexpr float kExposureSatisfactory = 0.2;\n-\n-/*\n- * Proportional gain for exposure/gain adjustment. Maps the MSV error to a\n- * multiplicative correction factor:\n- *\n- *   factor = 1.0 + kExpProportionalGain * error\n- *\n- * With kExpProportionalGain = 0.04:\n- *   - max error ~2.5 -> factor 1.10 (~10% step, same as before)\n- *   - error 1.0      -> factor 1.04 (~4% step)\n- *   - error 0.3      -> factor 1.012 (~1.2% step)\n- *\n- * This replaces the fixed 10% bang-bang step with a proportional correction\n- * that converges smoothly and avoids overshooting near the target.\n- */\n-static constexpr float kExpProportionalGain = 0.04;\n-\n-/*\n- * Maximum multiplicative step per frame, to bound the correction when the\n- * scene changes dramatically.\n- */\n-static constexpr float kExpMaxStep = 0.15;\n-\n-namespace {\n-\n-std::optional<float> calculateMSV(const Histogram &histogram)\n-{\n-\t/*\n-\t * Calculate Mean Sample Value (MSV) according to formula from:\n-\t * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf\n-\t */\n-\tconst unsigned int yHistValsPerBin = histogram.bins() / kExposureBinsCount;\n-\tconst unsigned int yHistValsPerBinMod =\n-\t\thistogram.bins() / (histogram.bins() % kExposureBinsCount + 1);\n-\tint exposureBins[kExposureBinsCount] = {};\n-\tunsigned int denom = 0;\n-\tunsigned int num = 0;\n-\n-\tif (yHistValsPerBin == 0)\n-\t\treturn {};\n-\n-\tfor (unsigned int i = 0; i < histogram.bins(); i++) {\n-\t\tunsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;\n-\t\texposureBins[idx] += histogram[i];\n-\t}\n-\n-\tfor (unsigned int i = 0; i < kExposureBinsCount; i++) {\n-\t\tLOG(IPASoftExposure, Debug) << i << \": \" << exposureBins[i];\n-\t\tdenom += exposureBins[i];\n-\t\tnum += exposureBins[i] * (i + 1);\n-\t}\n-\n-\treturn (denom == 0 ? 0 : static_cast<float>(num) / denom);\n-}\n-\n-} /* namespace */\n-\n-Agc::Agc()\n+int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo)\n {\n-}\n-\n-void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)\n-{\n-\tuint32_t exposure = frameContext.sensor.exposure;\n-\tdouble again = frameContext.sensor.gain;\n-\n-\tdouble error = kExposureOptimal - exposureMSV;\n-\n-\tif (std::abs(error) <= kExposureSatisfactory)\n-\t\treturn;\n-\n-\t/*\n-\t * Compute a proportional correction factor. The sign of the error\n-\t * determines the direction: positive error means too dark (increase),\n-\t * negative means too bright (decrease).\n-\t */\n-\tfloat step = std::clamp(static_cast<float>(error) * kExpProportionalGain,\n-\t\t\t\t-kExpMaxStep, kExpMaxStep);\n-\tfloat factor = 1.0f + step;\n-\n-\tif (factor > 1.0f) {\n-\t\t/* Scene too dark: increase exposure first, then gain. */\n-\t\tif (exposure < context.configuration.agc.exposureMax) {\n-\t\t\tuint32_t next = exposure * factor;\n-\t\t\texposure = std::max(next, exposure + 1);\n-\t\t} else {\n-\t\t\tdouble next = again * factor;\n-\t\t\tagain = std::max(next, again + context.configuration.agc.againMinStep);\n-\t\t}\n-\t} else {\n-\t\t/* Scene too bright: decrease gain first, then exposure. */\n-\t\tif (again > context.configuration.agc.again10) {\n-\t\t\tdouble next = again * factor;\n-\t\t\tagain = std::max(next, again - context.configuration.agc.againMinStep);\n-\t\t} else {\n-\t\t\tuint32_t next = exposure * factor;\n-\t\t\texposure = std::min(next, exposure - 1);\n-\t\t}\n-\t}\n-\n-\texposure = std::clamp(exposure, context.configuration.agc.exposureMin,\n-\t\t\t      context.configuration.agc.exposureMax);\n-\tagain = std::clamp(again, context.configuration.agc.againMin,\n-\t\t\t   context.configuration.agc.againMax);\n-\n-\tframeContext.agc.exposure = exposure;\n-\tframeContext.agc.gain = again;\n-\n-\tcontext.activeState.agc.exposure = exposure;\n-\tcontext.activeState.agc.again = again;\n-\n-\tLOG(IPASoftExposure, Debug)\n-\t\t<< \"exposureMSV \" << exposureMSV\n-\t\t<< \" error \" << error << \" factor \" << factor\n-\t\t<< \" exp \" << exposure << \" again \" << again;\n+\tagc_.setLimits({\n+\t\t.exposure = {\n+\t\t\tcontext.configuration.agc.exposureMin,\n+\t\t\tcontext.configuration.agc.exposureMax,\n+\t\t},\n+\t\t.gain = {\n+\t\t\tcontext.configuration.agc.againMin,\n+\t\t\tcontext.configuration.agc.againMax,\n+\t\t},\n+\t\t.gainMinStep = context.configuration.agc.againMinStep,\n+\t\t.gain1 = context.configuration.agc.again10,\n+\t});\n+\n+\treturn 0;\n }\n \n void Agc::process(IPAContext &context,\n@@ -197,14 +77,17 @@ void Agc::process(IPAContext &context,\n \tfor (unsigned int i = 1; i < blackLevelHistIdx; i++)\n \t\thistogram[0] += std::exchange(histogram[i], 0);\n \n-\tauto exposureMSV = calculateMSV({ histogram });\n-\tif (!exposureMSV) {\n-\t\tLOG(IPASoftExposure, Debug)\n-\t\t\t<< \"Not adjusting exposure due to insufficient histogram data\";\n-\t\treturn;\n-\t}\n+\tconst auto &newEv = agc_.calculateNewEv({\n+\t\t.yHist = { histogram },\n+\t\t.exposure = frameContext.sensor.exposure,\n+\t\t.gain = frameContext.sensor.gain,\n+\t});\n+\n+\tframeContext.agc.exposure = newEv.exposure;\n+\tframeContext.agc.gain = newEv.analogueGain;\n \n-\tupdateExposure(context, frameContext, *exposureMSV);\n+\tcontext.activeState.agc.exposure = frameContext.agc.exposure;\n+\tcontext.activeState.agc.again = frameContext.agc.gain;\n }\n \n REGISTER_IPA_ALGORITHM(Agc, \"Agc\")\ndiff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h\nindex 112d9f5a19..2e156e135c 100644\n--- a/src/ipa/simple/algorithms/agc.h\n+++ b/src/ipa/simple/algorithms/agc.h\n@@ -9,6 +9,8 @@\n \n #include \"algorithm.h\"\n \n+#include <libipa/agc_msv.h>\n+\n namespace libcamera {\n \n namespace ipa::soft::algorithms {\n@@ -16,8 +18,7 @@ namespace ipa::soft::algorithms {\n class Agc : public Algorithm\n {\n public:\n-\tAgc();\n-\t~Agc() = default;\n+\tint configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n \n \tvoid process(IPAContext &context, const uint32_t frame,\n \t\t     IPAFrameContext &frameContext,\n@@ -25,7 +26,7 @@ public:\n \t\t     ControlList &metadata) override;\n \n private:\n-\tvoid updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV);\n+\tAgcMSV agc_;\n };\n \n } /* namespace ipa::soft::algorithms */\n","prefixes":["RFC","v2","37/43"]}