[{"id":39635,"web_url":"https://patchwork.libcamera.org/comment/39635/","msgid":"<854ii9eiqt.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2026-07-08T13:03:22","subject":"Re: [PATCH v4 03/35] ipa: simple: awb: Port to use libipa\n\tAwbAlgorithm","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Hi Jacopo,\n\nJacopo Mondi <jacopo.mondi@ideasonboard.com> writes:\n\n> From: Kieran Bingham <kieran.bingham@ideasonboard.com>\n>\n> Port the SoftISP Awb algorithm to use the new libipa implementation\n> of AwbAlgorithm.\n>\n> The awbAlgo_ class member is initialized with the Q<4, 8> type even if\n> there is no physical register representation for SoftISP.\n>\n> The usage of libipa::awb::Context, which defines the gains vector as\n> RGB<double>, in the SimpleIPA ActiveState and FrameContext requires\n> changes to debayer_cpu.cpp and blc.cpp in order not to mix RGB<float>\n> and RGB<double>.\n>\n> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> Tested-by: Robert Mader <robert.mader@collabora.com>\n> ---\n>  .../internal/software_isp/debayer_params.h         |   4 +-\n>  src/ipa/simple/algorithms/awb.cpp                  | 131 +++++++++++++++------\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, 141 insertions(+), 51 deletions(-)\n>\n> diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h\n> index 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 */\n> diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp\n> index 05155c83d172..e8c965df65fa 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,98 @@ 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\nrgbMeans_ is undeclared and the compilation fails.  There are other\ncompilation issues in `simple' pipeline.  All regressions from v3,\nplease check.\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> +\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> +\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 +123,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\")\n> diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h\n> index 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 */\n> diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp\n> index 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,\n> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\n> index ace9c35dc462..ff37c718c6e4 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.temperatureK;\n>  \n>  \t/* Change CCM only on bigger temperature changes. */\n>  \tif (!currentCcm_ ||\n> diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\n> index 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;\n> diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\n> index 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 &params)\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 &params)\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 &params)\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()];","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 550E8C3306\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  8 Jul 2026 13:03:38 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 65EB366088;\n\tWed,  8 Jul 2026 15:03:37 +0200 (CEST)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.129.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D030A65FF1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  8 Jul 2026 15:03:35 +0200 (CEST)","from mail-wm1-f72.google.com (mail-wm1-f72.google.com\n\t[209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-369-_4xww2uJO52KbD5OtYYUug-1; Wed, 08 Jul 2026 09:03:33 -0400","by mail-wm1-f72.google.com with SMTP id\n\t5b1f17b1804b1-493a6fe9ebdso5117285e9.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 08 Jul 2026 06:03:33 -0700 (PDT)","from mzamazal-thinkpadp1gen7.tpbc.csb\n\t(ip-77-48-47-4.net.vodafone.cz. [77.48.47.4])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-493e0f50418sm124860775e9.11.2026.07.08.06.03.26\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 08 Jul 2026 06:03:30 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"Mim5vSDb\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1783515814;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=nO9qbD4hxWPzF7uPa9cFq718+mr6+ITuDAE3Djlal7s=;\n\tb=Mim5vSDbfquugNcPM6p8I2FDrrE0Nr1gfG9hDGPMgBLA471CYEAYSrfuOxVVkQhX6hUtX3\n\tAl4DM4f0srZ0d1qT0AuMeA80ZZBXghZTKaUnoL7afs+Rc5guThuPeuFWUOTMkQReaSy3yR\n\tgSLh5HXKsjApZ9wuCL6IlU3B1u2XEJg=","X-MC-Unique":"_4xww2uJO52KbD5OtYYUug-1","X-Mimecast-MFC-AGG-ID":"_4xww2uJO52KbD5OtYYUug_1783515812","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20251104; t=1783515812; x=1784120612;\n\th=content-type:mime-version:user-agent:message-id:date:references\n\t:in-reply-to:subject:cc:to:from:x-gm-gg:x-gm-message-state:from:to\n\t:cc:subject:date:message-id:reply-to:content-type;\n\tbh=nO9qbD4hxWPzF7uPa9cFq718+mr6+ITuDAE3Djlal7s=;\n\tb=bO2oV91h/Cga9pnXsqxLfOPleekNWEYhP3pYpnUw9xQllkfwHtoOelYc0zP3eP953y\n\tQXvx923ZC31elkGTfKmPxzF1i2WbSQLKl0TaE3dqwvhBQ75rK8ZuxapOinnFksZOZF5P\n\tnpfUvmQg5L3WQ4GJXCRWsCsgpVUiUF93P+dsPNQx5mc/W+5RaTU3SAt0EKLEbSw5ryCF\n\tdZH396cVs0CzLkGeVv3AiMYIwDAKCOMqt0fATFxdRC5XaMV2o3kwRdyREVK0tM2F6fLX\n\tS7BmZ3dSlVB/04TFRKTs3sVBSwaR5R+JPItGR2M7hjwKQKEnAWAX/kls2g0EzP/kEtfi\n\tT6Jg==","X-Gm-Message-State":"AOJu0YzDE1iIhg93ZQ7AWPbkx2fNMZ9DrFygJiTYxuFNpP1L3ADgij01\n\txI375raBK1MVyRWTVGUSOtddpJF46IsRDzJzbirIAajMl3pxy4Agxk664+PGGp1MkMyaGecKj5D\n\t+GcHSlJdgMeWBQNBs97g3TJcfkoPbNLBnNehpuK7KOrto2glyA9MfFGaHMqwOo+ZSOQAS2iiFKk\n\tY=","X-Gm-Gg":"AfdE7cmdgKQbN0eUTRae2VDRDY3ok3dO2Lvc8R0ncRs6THw1R/BHLQw+TXKDhXd6IXo\n\tLFvICJFyV8KEt4LjPBFjWcyzQwzofjuPy4IboKLgHJNT4gUW/tI1vPZiS8dBl1AqbxSyfO4Dbgs\n\tPxk+KTfNpHkEvtwVfXbLASrRwhdDiFvbL8RBvS5r41h3WxlxX1d8NM66voaEwdiShYT3Jx77kOp\n\tnBZ1aDpFti8u1XD81rilgS0RpMmkElxpmDkcTENUtHv0JCSo/HCvzs040VbCOgaBooIBHCJTLTL\n\t5zZi1Eor+uDN8ABgKHv8LlsRC6vPSOiuigptCSu4mOWbqlfzU8MPcI1IlfCnc23BIHfybuBjup5\n\tgnOz75HdlcxWBshlWfoh6ucP3cV/f4WQXu3ZlnCHIkegEmn8I9QvI0CKDN5X/9bX3","X-Received":["by 2002:a05:600c:1da8:b0:492:63c3:8eeb with SMTP id\n\t5b1f17b1804b1-493e687cb4amr21475715e9.35.1783515811800; \n\tWed, 08 Jul 2026 06:03:31 -0700 (PDT)","by 2002:a05:600c:1da8:b0:492:63c3:8eeb with SMTP id\n\t5b1f17b1804b1-493e687cb4amr21475115e9.35.1783515811049; \n\tWed, 08 Jul 2026 06:03:31 -0700 (PDT)"],"From":"Milan Zamazal <mzamazal@redhat.com>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org,  Stefan Klug\n\t<stefan.klug@ideasonboard.com>,  Kieran Bingham\n\t<kieran.bingham@ideasonboard.com>,  Robert Mader\n\t<robert.mader@collabora.com>","Subject":"Re: [PATCH v4 03/35] ipa: simple: awb: Port to use libipa\n\tAwbAlgorithm","In-Reply-To":"<20260708-libipa-algorithms-v4-3-f0e99f035294@ideasonboard.com>\n\t(Jacopo Mondi's message of \"Wed, 08 Jul 2026 11:24:50 +0200\")","References":"<20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com>\n\t<20260708-libipa-algorithms-v4-3-f0e99f035294@ideasonboard.com>","Date":"Wed, 08 Jul 2026 15:03:22 +0200","Message-ID":"<854ii9eiqt.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"BUBOKg2scrZMg-A5PdCg4lnTdLIkWQV2M58Cex2xk8I_1783515812","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","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>"}},{"id":39637,"web_url":"https://patchwork.libcamera.org/comment/39637/","msgid":"<ak5RB1TFFFlABgXx@zed>","date":"2026-07-08T13:31:48","subject":"Re: [PATCH v4 03/35] ipa: simple: awb: Port to use libipa\n\tAwbAlgorithm","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Milan\n\nOn Wed, Jul 08, 2026 at 03:03:22PM +0200, Milan Zamazal wrote:\n> Hi Jacopo,\n>\n> Jacopo Mondi <jacopo.mondi@ideasonboard.com> writes:\n>\n> > From: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> >\n> > Port the SoftISP Awb algorithm to use the new libipa implementation\n> > of AwbAlgorithm.\n> >\n> > The awbAlgo_ class member is initialized with the Q<4, 8> type even if\n> > there is no physical register representation for SoftISP.\n> >\n> > The usage of libipa::awb::Context, which defines the gains vector as\n> > RGB<double>, in the SimpleIPA ActiveState and FrameContext requires\n> > changes to debayer_cpu.cpp and blc.cpp in order not to mix RGB<float>\n> > and RGB<double>.\n> >\n> > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> > Tested-by: Robert Mader <robert.mader@collabora.com>\n> > ---\n> >  .../internal/software_isp/debayer_params.h         |   4 +-\n> >  src/ipa/simple/algorithms/awb.cpp                  | 131 +++++++++++++++------\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, 141 insertions(+), 51 deletions(-)\n> >\n> > diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h\n> > index 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 */\n> > diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp\n> > index 05155c83d172..e8c965df65fa 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,98 @@ 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> rgbMeans_ is undeclared and the compilation fails.  There are other\n> compilation issues in `simple' pipeline.  All regressions from v3,\n> please check.\n\nOh crabs, I forgot to update the simple ph to the latest updates of\nAwbStats -_-\n\nSorry for the noise, I'll make sure I'll compile Simple as well :(\n\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> > +\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> > +\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 +123,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\")\n> > diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h\n> > index 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 */\n> > diff --git a/src/ipa/simple/algorithms/blc.cpp b/src/ipa/simple/algorithms/blc.cpp\n> > index 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,\n> > diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\n> > index ace9c35dc462..ff37c718c6e4 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.temperatureK;\n> >\n> >  \t/* Change CCM only on bigger temperature changes. */\n> >  \tif (!currentCcm_ ||\n> > diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\n> > index 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;\n> > diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp\n> > index 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 &params)\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 &params)\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 &params)\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>","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 9FECEC3304\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  8 Jul 2026 13:31:53 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E804C66080;\n\tWed,  8 Jul 2026 15:31:52 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id BCF4A66080\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  8 Jul 2026 15:31:51 +0200 (CEST)","from ideasonboard.com (93-46-82-201.ip106.fastwebnet.it\n\t[93.46.82.201])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 793B819A2;\n\tWed,  8 Jul 2026 15:31:01 +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=\"aFEFJgX3\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1783517461;\n\tbh=OzYJrH7ShB68xCUGNPIenZgOY6NW9Kzz7xWJf/u4RaY=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=aFEFJgX380x5X0AhQBY/X/iQLGxN/0qZREAC5ZauSV02zN+CZk0ogywofy7tUyogR\n\t82X7amH7oHUNd7P0+wHiEC/4PAk2ffyvZ2yvI3Z32C/nbLhnXNVuTezeoTOzzUK+7R\n\tYFYcF+wZk7CIyuTEpSzxM+fBHPbdtPWt1rFbqLHo=","Date":"Wed, 8 Jul 2026 15:31:48 +0200","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"Milan Zamazal <mzamazal@redhat.com>","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org,\n\tStefan Klug <stefan.klug@ideasonboard.com>, \n\tKieran Bingham <kieran.bingham@ideasonboard.com>,\n\tRobert Mader <robert.mader@collabora.com>","Subject":"Re: [PATCH v4 03/35] ipa: simple: awb: Port to use libipa\n\tAwbAlgorithm","Message-ID":"<ak5RB1TFFFlABgXx@zed>","References":"<20260708-libipa-algorithms-v4-0-f0e99f035294@ideasonboard.com>\n\t<20260708-libipa-algorithms-v4-3-f0e99f035294@ideasonboard.com>\n\t<854ii9eiqt.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<854ii9eiqt.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","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>"}}]