Patch Detail
Show a patch.
GET /api/1.1/patches/27311/?format=api
{ "id": 27311, "url": "https://patchwork.libcamera.org/api/1.1/patches/27311/?format=api", "web_url": "https://patchwork.libcamera.org/patch/27311/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20260708-libipa-algorithms-v5-3-0759d0359f52@ideasonboard.com>", "date": "2026-07-08T15:50:45", "name": "[v5,03/36] ipa: simple: awb: Port to use libipa AwbAlgorithm", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "c1f5fe5c9be1119ad0cd8c97d6450f120b4c664f", "submitter": { "id": 143, "url": "https://patchwork.libcamera.org/api/1.1/people/143/?format=api", "name": "Jacopo Mondi", "email": "jacopo.mondi@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/27311/mbox/", "series": [ { "id": 6048, "url": "https://patchwork.libcamera.org/api/1.1/series/6048/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=6048", "date": "2026-07-08T15:50:42", "name": "ipa: libipa: Introduce libipa algorithms", "version": 5, "mbox": "https://patchwork.libcamera.org/series/6048/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/27311/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/27311/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 8EA48C3261\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 8 Jul 2026 15:51:23 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D482B660DB;\n\tWed, 8 Jul 2026 17:51:19 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DFAB0660B1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 8 Jul 2026 17:51:12 +0200 (CEST)", "from [192.168.1.182] (93-46-82-201.ip106.fastwebnet.it\n\t[93.46.82.201])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id ECA171BCF;\n\tWed, 8 Jul 2026 17:50:22 +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=\"ijfQdLuc\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1783525823;\n\tbh=kmNUq70BbFCrmBu5/t0w+AafCluJg95fYiP6PaPrpb8=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n\tb=ijfQdLucUEiGTBCXgMCMtDOb/Y0I4UN9zQg2aXF1WXdM39AOlJ4QlYd+e7L+rPBZE\n\taszVeJjoJJc07cp9ToF6xx1QYDS4BoVtvkyN3zkwF0rHkvEY5LCIgQBLXYJpyBbzNL\n\tX+NqG9QZzzFmj7/JFtV1e1a7cfw1pB86SRGhvjs8=", "From": "Jacopo Mondi <jacopo.mondi@ideasonboard.com>", "Date": "Wed, 08 Jul 2026 17:50:45 +0200", "Subject": "[PATCH v5 03/36] ipa: simple: awb: Port to use libipa AwbAlgorithm", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=\"utf-8\"", "Content-Transfer-Encoding": "7bit", "Message-Id": "<20260708-libipa-algorithms-v5-3-0759d0359f52@ideasonboard.com>", "References": "<20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com>", "In-Reply-To": "<20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com>", "To": "libcamera-devel@lists.libcamera.org, \n\tStefan Klug <stefan.klug@ideasonboard.com>", "Cc": "Jacopo Mondi <jacopo.mondi@ideasonboard.com>, \n\tKieran Bingham <kieran.bingham@ideasonboard.com>, \n\tRobert Mader <robert.mader@collabora.com>", "X-Mailer": "b4 0.14.3", "X-Developer-Signature": "v=1; a=openpgp-sha256; l=13677;\n\ti=jacopo.mondi@ideasonboard.com; h=from:subject:message-id;\n\tbh=jPiJcbEiCJucyZ6BJxSpVNb+9LFJKY6hvOG/cwFj3Ms=;\n\tb=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqTnHg9mUr7Dq+VrsXAg7KTF3xw0lhzcBnvZcOk\n\t+GOe31Oz4GJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCak5x4AAKCRByNAaPFqFW\n\tPKUMEADBPSpuIvdOoD9tH6Df1dIhoe4miI+YXMjYijN3p55YbKUhKYQ30hkglhFb2Z16vvxcw6u\n\tPYsk92auLykzoNPtO4owllI/5bqwdiFQ7nea/3/3wNxsobI3NUFBrIoKe/Q40XueDylpOEgGdEp\n\t9Gf8Yk02ca79nbJKvrPMPO/6/kzVSTR3slz11u8p1162yRQUKuUseVukKBZgga7a1glehGzR3T6\n\tMSTUVpLmXJiJGPMQh2oh5vBaX96S4Zdftt8ZjsyjWqwhPb+VwNDRoBctq9QbtGVN9vjzqwFUnhj\n\tNEmItJPb1ZpTe5WzJnCw3PSJAWRz5f1aU6Hc2dqVq45GlJwPsUaoYDN9/yCYPg3mjnxAcxFymcO\n\t82FZeLc5/fyba44ZUtBrtdJZhD1BmToP5zmsh4YFCCfbzhPe3M4Zd1t+NZnQVQIP68tgj2e1B9I\n\tv0+zKK+sl95bOsa32xkWUup/Yf6HJNVTBl/qocZB10x/W3lhy+41ozhizMTponPifwO/z49tSgF\n\tkVZkFd/Re5Jy7Aq45GmmDRwH/0UQYkGlH799PEWcBr9YIHjiMbDje4JkMju+HhGs87Ng4SU2Xeq\n\t9NCSe2WclZY8rqVnZcjnw3yrzZijF1F/zs9yI0h5pG7KzQCBJWT+hvtf1iupKXWOYx5Ldz/jDCy\n\tr12Fxj+AK5gddLw==", "X-Developer-Key": "i=jacopo.mondi@ideasonboard.com; a=openpgp;\n\tfpr=72392EDC88144A65C701EA9BA5826A2587AD026B", "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": "From: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\nPort the SoftISP Awb algorithm to use the new libipa implementation\nof AwbAlgorithm.\n\nThe awbAlgo_ class member is initialized with the Q<4, 8> type even if\nthere is no physical register representation for SoftISP.\n\nThe usage of libipa::awb::Context, which defines the gains vector as\nRGB<double>, in the SimpleIPA ActiveState and FrameContext requires\nchanges to debayer_cpu.cpp and blc.cpp in order not to mix RGB<float>\nand RGB<double>.\n\nSigned-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\nSigned-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\nTested-by: Robert Mader <robert.mader@collabora.com>\n---\n .../internal/software_isp/debayer_params.h | 4 +-\n src/ipa/simple/algorithms/awb.cpp | 141 ++++++++++++++++-----\n src/ipa/simple/algorithms/awb.h | 29 +++++\n src/ipa/simple/algorithms/blc.cpp | 2 +-\n src/ipa/simple/algorithms/ccm.cpp | 2 +-\n src/ipa/simple/ipa_context.h | 12 +-\n src/libcamera/software_isp/debayer_cpu.cpp | 12 +-\n 7 files changed, 151 insertions(+), 51 deletions(-)", "diff": "diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h\nindex 6772b43bced4..1074720d73c2 100644\n--- a/include/libcamera/internal/software_isp/debayer_params.h\n+++ b/include/libcamera/internal/software_isp/debayer_params.h\n@@ -21,10 +21,10 @@ struct DebayerParams {\n \tMatrix<float, 3, 3> combinedMatrix = { { 1.0, 0.0, 0.0,\n \t\t\t\t\t\t 0.0, 1.0, 0.0,\n \t\t\t\t\t\t 0.0, 0.0, 1.0 } };\n-\tRGB<float> blackLevel = RGB<float>({ 0.0, 0.0, 0.0 });\n+\tRGB<double> blackLevel = RGB<double>({ 0.0, 0.0, 0.0 });\n \tfloat gamma = 1.0;\n \tfloat contrastExp = 1.0;\n-\tRGB<float> gains = RGB<float>({ 1.0, 1.0, 1.0 });\n+\tRGB<double> gains = RGB<double>({ 1.0, 1.0, 1.0 });\n };\n \n } /* namespace libcamera */\ndiff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp\nindex 05155c83d172..64ce4f91b21c 100644\n--- a/src/ipa/simple/algorithms/awb.cpp\n+++ b/src/ipa/simple/algorithms/awb.cpp\n@@ -15,7 +15,6 @@\n #include <libcamera/control_ids.h>\n \n #include \"libipa/colours.h\"\n-#include \"simple/ipa_context.h\"\n \n namespace libcamera {\n \n@@ -23,41 +22,108 @@ LOG_DEFINE_CATEGORY(IPASoftAwb)\n \n namespace ipa::soft::algorithms {\n \n+/*\n+ * \\todo Replace it with a proper Lux algorithm\n+ */\n+static constexpr unsigned int kDefaultLux = 500;\n+\n+class SimpleAwbStats final : public AwbStats\n+{\n+public:\n+\tSimpleAwbStats() = default;\n+\n+\tSimpleAwbStats(const RGB<double> &rgbMeans)\n+\t{\n+\t\trgbMeans_ = rgbMeans;\n+\n+\t\trg_ = rgbMeans_.r() / rgbMeans_.g();\n+\t\tbg_ = rgbMeans_.b() / rgbMeans_.g();\n+\t}\n+\n+\tdouble computeColourError(const RGB<double> &gains) const override\n+\t{\n+\t\t/*\n+\t\t * Compute the sum of the squared colour error (non-greyness) as\n+\t\t * it appears in the log likelihood equation.\n+\t\t */\n+\t\tdouble deltaR = gains.r() * rg_ - 1.0;\n+\t\tdouble deltaB = gains.b() * bg_ - 1.0;\n+\t\tdouble delta2 = deltaR * deltaR + deltaB * deltaB;\n+\n+\t\treturn delta2;\n+\t}\n+\n+\tRGB<double> rgbMeans() const override\n+\t{\n+\t\treturn rgbMeans_;\n+\t}\n+\n+\tbool valid() const override\n+\t{\n+\t\t/* Minimum mean value below which AWB can't operate. */\n+\t\tconstexpr double minValue = 0.2;\n+\n+\t\treturn rgbMeans_.r() > minValue || rgbMeans_.g() > minValue ||\n+\t\t rgbMeans_.b() > minValue;\n+\t}\n+\n+private:\n+\tRGB<double> rgbMeans_;\n+\tdouble rg_;\n+\tdouble bg_;\n+};\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::init\n+ */\n+int Awb::init(IPAContext &context, const ValueNode &tuningData)\n+{\n+\treturn awbAlgo_.init(tuningData, context.ctrlMap);\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::configure\n+ */\n int Awb::configure(IPAContext &context,\n \t\t [[maybe_unused]] const IPAConfigInfo &configInfo)\n {\n-\tauto &gains = context.activeState.awb.gains;\n-\tgains = { { 1.0, 1.0, 1.0 } };\n+\treturn awbAlgo_.configure(context.activeState.awb);\n+}\n \n-\treturn 0;\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::queueRequest\n+ */\n+void Awb::queueRequest(IPAContext &context,\n+\t\t const uint32_t frame,\n+\t\t IPAFrameContext &frameContext,\n+\t\t const ControlList &controls)\n+{\n+\tawbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,\n+\t\t\t controls);\n }\n \n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::prepare\n+ */\n void Awb::prepare(IPAContext &context,\n \t\t [[maybe_unused]] const uint32_t frame,\n \t\t IPAFrameContext &frameContext,\n \t\t DebayerParams *params)\n {\n-\tauto &gains = context.activeState.awb.gains;\n+\tawbAlgo_.prepare(context.activeState.awb, frameContext.awb);\n \n-\tframeContext.gains = gains;\n-\tparams->gains = gains;\n+\tparams->gains = frameContext.awb.gains;\n }\n \n-void Awb::process(IPAContext &context,\n-\t\t [[maybe_unused]] const uint32_t frame,\n-\t\t IPAFrameContext &frameContext,\n-\t\t const SwIspStats *stats,\n-\t\t ControlList &metadata)\n+SimpleAwbStats Awb::calculateRgbMeans(IPAContext &context,\n+\t\t\t\t const SwIspStats *stats) const\n {\n+\tif (!stats->valid)\n+\t\treturn {};\n+\n \tconst SwIspStats::Histogram &histogram = stats->yHistogram;\n \tconst uint8_t blackLevel = context.activeState.blc.level;\n \n-\tmetadata.set(controls::ColourGains, { frameContext.gains.r(),\n-\t\t\t\t\t frameContext.gains.b() });\n-\n-\tif (!stats->valid)\n-\t\treturn;\n-\n \t/*\n \t * Black level must be subtracted to get the correct AWB ratios, they\n \t * would be off if they were computed from the whole brightness range\n@@ -67,30 +133,37 @@ void Awb::process(IPAContext &context,\n \t\thistogram.begin(), histogram.end(), uint64_t(0));\n \tconst uint64_t offset = blackLevel * nPixels;\n \tconst uint64_t minValid = 1;\n+\n \t/*\n \t * Make sure the sums are at least minValid, while preventing unsigned\n \t * integer underflow.\n \t */\n \tconst RGB<uint64_t> sum = stats->sum_.max(offset + minValid) - offset;\n \n+\tRGB<double> rgbMeans = { { static_cast<double>(sum.r() / nPixels),\n+\t\t\t\t static_cast<double>(sum.g() / nPixels),\n+\t\t\t\t static_cast<double>(sum.b() / nPixels) } };\n+\n \t/*\n-\t * Calculate red and blue gains for AWB.\n-\t * Clamp max gain at 4.0, this also avoids 0 division.\n+\t * \\todo Determine the minimum allowed thresholds from the mean\n+\t * but we currently have the sum - not the mean value!\n+\t *\n+\t * Currently set to SimpleAwbStats::minColourValue() = 0.2.\n \t */\n-\tauto &gains = context.activeState.awb.gains;\n-\tgains = { {\n-\t\tsum.r() <= sum.g() / 4 ? 4.0f : static_cast<float>(sum.g()) / sum.r(),\n-\t\t1.0,\n-\t\tsum.b() <= sum.g() / 4 ? 4.0f : static_cast<float>(sum.g()) / sum.b(),\n-\t} };\n-\n-\tRGB<double> rgbGains{ { 1 / gains.r(), 1 / gains.g(), 1 / gains.b() } };\n-\tcontext.activeState.awb.temperatureK = estimateCCT(rgbGains);\n-\tmetadata.set(controls::ColourTemperature, context.activeState.awb.temperatureK);\n-\n-\tLOG(IPASoftAwb, Debug)\n-\t\t<< \"gain R/B: \" << gains << \"; temperature: \"\n-\t\t<< context.activeState.awb.temperatureK;\n+\treturn SimpleAwbStats(rgbMeans);\n+}\n+\n+/**\n+ * \\copydoc libcamera::ipa::Algorithm::process\n+ */\n+void Awb::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n+\t\t IPAFrameContext &frameContext, const SwIspStats *stats,\n+\t\t ControlList &metadata)\n+{\n+\tSimpleAwbStats awbStats = calculateRgbMeans(context, stats);\n+\n+\tawbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,\n+\t\t\t kDefaultLux, metadata);\n }\n \n REGISTER_IPA_ALGORITHM(Awb, \"Awb\")\ndiff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h\nindex ad993f39c180..fec48c6eea1c 100644\n--- a/src/ipa/simple/algorithms/awb.h\n+++ b/src/ipa/simple/algorithms/awb.h\n@@ -7,19 +7,37 @@\n \n #pragma once\n \n+#include <libcamera/controls.h>\n+\n+#include \"libcamera/internal/software_isp/debayer_params.h\"\n+#include \"libcamera/internal/value_node.h\"\n+\n+#include \"libipa/awb.h\"\n+#include \"libipa/fixedpoint.h\"\n+#include \"simple/ipa_context.h\"\n+\n #include \"algorithm.h\"\n \n namespace libcamera {\n \n namespace ipa::soft::algorithms {\n \n+class SimpleAwbStats;\n+\n class Awb : public Algorithm\n {\n public:\n \tAwb() = default;\n \t~Awb() = default;\n \n+\tint init(IPAContext &context,\n+\t\t const ValueNode &tuningData) override;\n \tint configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n+\n+\tvoid queueRequest(IPAContext &context,\n+\t\t\t [[maybe_unused]] const uint32_t frame,\n+\t\t\t IPAFrameContext &frameContext,\n+\t\t\t const ControlList &controls) override;\n \tvoid prepare(IPAContext &context,\n \t\t const uint32_t frame,\n \t\t IPAFrameContext &frameContext,\n@@ -29,6 +47,17 @@ public:\n \t\t IPAFrameContext &frameContext,\n \t\t const SwIspStats *stats,\n \t\t ControlList &metadata) override;\n+\n+private:\n+\tSimpleAwbStats calculateRgbMeans(IPAContext &context,\n+\t\t\t\t\t const SwIspStats *stats) const;\n+\n+\t/*\n+\t * There actually is no Q register format for SoftISP, but allow the\n+\t * colour gains to range in the [0.0f, 3.999f] interval, which seems\n+\t * reasonable.\n+\t */\n+\tAwbAlgorithm<UQ<2, 8>> awbAlgo_;\n };\n \n } /* namespace ipa::soft::algorithms */\ndiff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp\nindex 677be56ed669..e45a913cd970 100644\n--- a/src/ipa/simple/algorithms/blc.cpp\n+++ b/src/ipa/simple/algorithms/blc.cpp\n@@ -53,7 +53,7 @@ void BlackLevel::prepare(IPAContext &context,\n \t\t\t DebayerParams *params)\n {\n \t/* Latch the blacklevel gain so GPUISP can apply. */\n-\tparams->blackLevel = RGB<float>(context.activeState.blc.level / 255.0f);\n+\tparams->blackLevel = RGB<double>(context.activeState.blc.level / 255.0f);\n }\n \n void BlackLevel::process(IPAContext &context,\ndiff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\nindex ace9c35dc462..1174784edc7e 100644\n--- a/src/ipa/simple/algorithms/ccm.cpp\n+++ b/src/ipa/simple/algorithms/ccm.cpp\n@@ -44,7 +44,7 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)\n void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n \t\t IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params)\n {\n-\tconst unsigned int ct = context.activeState.awb.temperatureK;\n+\tconst unsigned int ct = frameContext.awb.colourTemperature;\n \n \t/* Change CCM only on bigger temperature changes. */\n \tif (!currentCcm_ ||\ndiff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\nindex 8ccfacb46a59..29643a655ce1 100644\n--- a/src/ipa/simple/ipa_context.h\n+++ b/src/ipa/simple/ipa_context.h\n@@ -16,6 +16,7 @@\n #include \"libcamera/internal/matrix.h\"\n #include \"libcamera/internal/vector.h\"\n \n+#include <libipa/awb.h>\n #include <libipa/fc_queue.h>\n \n #include \"core_ipa_interface.h\"\n@@ -36,6 +37,8 @@ struct IPASessionConfiguration {\n };\n \n struct IPAActiveState {\n+\tipa::awb::ActiveState awb;\n+\n \tstruct {\n \t\tint32_t exposure;\n \t\tdouble again;\n@@ -48,11 +51,6 @@ struct IPAActiveState {\n \t\tdouble lastGain;\n \t} blc;\n \n-\tstruct {\n-\t\tRGB<float> gains;\n-\t\tunsigned int temperatureK;\n-\t} awb;\n-\n \tMatrix<float, 3, 3> combinedMatrix;\n \n \tstruct {\n@@ -64,6 +62,8 @@ struct IPAActiveState {\n };\n \n struct IPAFrameContext : public FrameContext {\n+\tipa::awb::FrameContext awb;\n+\n \tMatrix<float, 3, 3> ccm;\n \n \tstruct {\n@@ -71,8 +71,6 @@ struct IPAFrameContext : public FrameContext {\n \t\tdouble gain;\n \t} sensor;\n \n-\tRGB<float> gains;\n-\n \tfloat gamma;\n \tstd::optional<float> contrast;\n \tstd::optional<float> saturation;\ndiff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\nindex 49382b4c2719..ab22635fdfaf 100644\n--- a/src/libcamera/software_isp/debayer_cpu.cpp\n+++ b/src/libcamera/software_isp/debayer_cpu.cpp\n@@ -1010,9 +1010,9 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms)\n \t};\n \tconst unsigned int gammaTableSize = gammaTable_.size();\n \n-\tconst RGB<float> blackIndex = params.blackLevel * kRGBLookupSize;\n-\tconst RGB<float> gains = params.gains;\n-\tconst RGB<float> div = (RGB<float>(kRGBLookupSize) - blackIndex).max(1.0);\n+\tconst RGB<double> blackIndex = params.blackLevel * kRGBLookupSize;\n+\tconst RGB<double> gains = params.gains;\n+\tconst RGB<double> div = (RGB<double>(kRGBLookupSize) - blackIndex).max(1.0);\n \n \tif (ccmEnabled_) {\n \t\tif (gammaUpdateNeeded ||\n@@ -1025,7 +1025,7 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms)\n \t\t\tconst unsigned int greenIndex = 1;\n \t\t\tconst unsigned int blueIndex = swapRedBlueGains_ ? 0 : 2;\n \t\t\tfor (unsigned int i = 0; i < kRGBLookupSize; i++) {\n-\t\t\t\tconst RGB<float> rgb = (gains * (RGB<float>(i) - blackIndex) * kRGBLookupSize / div)\n+\t\t\t\tconst RGB<double> rgb = (gains * (RGB<double>(i) - blackIndex) * kRGBLookupSize / div)\n \t\t\t\t\t\t\t .clamp(0.0, kRGBLookupSize - 1);\n \t\t\t\tred[i].r = std::round(rgb.r() * params.combinedMatrix[redIndex][0]);\n \t\t\t\tred[i].g = std::round(rgb.r() * params.combinedMatrix[greenIndex][0]);\n@@ -1045,8 +1045,8 @@ void DebayerCpu::updateLookupTables(const DebayerParams ¶ms)\n \t\t\tauto &green = green_;\n \t\t\tauto &blue = swapRedBlueGains_ ? red_ : blue_;\n \t\t\tfor (unsigned int i = 0; i < kRGBLookupSize; i++) {\n-\t\t\t\tconst RGB<float> lutGains =\n-\t\t\t\t\t(gains * (RGB<float>(i) - blackIndex) * gammaTableSize / div)\n+\t\t\t\tconst RGB<double> lutGains =\n+\t\t\t\t\t(gains * (RGB<double>(i) - blackIndex) * gammaTableSize / div)\n \t\t\t\t\t\t.clamp(0.0, gammaTableSize - 1);\n \t\t\t\tred[i] = gammaTable_[lutGains.r()];\n \t\t\t\tgreen[i] = gammaTable_[lutGains.g()];\n", "prefixes": [ "v5", "03/36" ] }