Show a patch.

GET /api/1.1/patches/27201/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 27201,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/27201/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/27201/",
    "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": "<20260706-libipa-algorithms-v3-1-968757b038bb@ideasonboard.com>",
    "date": "2026-07-06T08:01:21",
    "name": "[v3,01/35] ipa: libipa: awb: Reimplement AwbAlgorithm",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "ac827358d7fb8f4f2e642ef9871021b047b75975",
    "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/27201/mbox/",
    "series": [
        {
            "id": 6038,
            "url": "https://patchwork.libcamera.org/api/1.1/series/6038/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=6038",
            "date": "2026-07-06T08:01:20",
            "name": "ipa: libipa: Introduce libipa algorithms",
            "version": 3,
            "mbox": "https://patchwork.libcamera.org/series/6038/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/27201/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/27201/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 1ABF5C3308\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  6 Jul 2026 08:02:02 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 255E965EFF;\n\tMon,  6 Jul 2026 10:02:00 +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 AA53C65EFF\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  6 Jul 2026 10:01:57 +0200 (CEST)",
            "from [192.168.125.177] (mob-109-113-60-81.net.vodafone.it\n\t[109.113.60.81])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 9ED2E16F9;\n\tMon,  6 Jul 2026 10:01:08 +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=\"ZcyT58iH\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1783324869;\n\tbh=T1+9aPkNpAvIevTiICbsnSYdxSzS36sKLICeNpG/+RE=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n\tb=ZcyT58iHDoC6iZI/V6x0bahfA/I6HUqIS5hZM6/Zd9dsPOkuQrH49K+DLxW/2PATu\n\tJbGRCGs62ZGtkydnKsNl/dFzDsRVYGfbnhoSWIy3EpJEa0RyJAdDN9SQFUbTXVnNAK\n\tvVQoozefwRnM/wzjli6AqXqdbLp40dJ4NKDW7AJI=",
        "From": "Jacopo Mondi <jacopo.mondi@ideasonboard.com>",
        "Date": "Mon, 06 Jul 2026 10:01:21 +0200",
        "Subject": "[PATCH v3 01/35] ipa: libipa: awb: Reimplement AwbAlgorithm",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=\"utf-8\"",
        "Content-Transfer-Encoding": "7bit",
        "Message-Id": "<20260706-libipa-algorithms-v3-1-968757b038bb@ideasonboard.com>",
        "References": "<20260706-libipa-algorithms-v3-0-968757b038bb@ideasonboard.com>",
        "In-Reply-To": "<20260706-libipa-algorithms-v3-0-968757b038bb@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\tDaniel Scally <dan.scally@ideasonboard.com>",
        "X-Mailer": "b4 0.14.3",
        "X-Developer-Signature": "v=1; a=openpgp-sha256; l=51483;\n\ti=jacopo.mondi@ideasonboard.com; h=from:subject:message-id;\n\tbh=T1+9aPkNpAvIevTiICbsnSYdxSzS36sKLICeNpG/+RE=;\n\tb=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBqS2DuTiFp73SZj5CqWVO3wT+IA3b3weKBqyxXt\n\tLvFcodfkkaJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaktg7gAKCRByNAaPFqFW\n\tPJefD/42ZrZ32X0+pxSTfAZ05B0KzRmPHE6eGgqzsa3cuxAe1+plpM1g9FXW1P75EcLNliJ00FQ\n\tjedgTmCKfAmBsHWYYRPvBc1BJab+JQofWgVFBg9t09zyqUYqSJTueojzw0ifKnlijCqQq0kazC1\n\tfFMdZ7hMkvU096CG4GY+1vyHTwf4Q2QFAj+aTW3dbvA4Gtt2WihDJLGIweePsE9DeCv5tyVhotX\n\tnDaFx2BvePNpLCNlTFMMQOb83jjuZQpJHuA6IpCmkLysQmPb23rp0si+7frjKE/rFJSmewohfKT\n\tGBKhjehZa54+ehlIyWqr/P2mtey17CnrOz9s3UEtf+GojNIgrR0/KZlshLI6wj9mjU2P4jd0CS/\n\tXahCNcnaNoUzXWuocEExefuZft8as1tTs7ekUEzObud4R82OXt7YB9SR6csvHsSw9r5K9qljsoO\n\tx/ITPCdinqlEHVaGpk3WJPpn8eCcwqEPte7AgO8NOXCqPO/9IzvEKSDOMrp815Lz3kk+FbvnW26\n\tXKovvchixDSlBOQq2PD8i88sNIWszJN/pGn4h1ZmhLUdseMkp+h4dfIQkGE7ICzzPcTZs7embfG\n\tEjkr1DJ+UQb68DmnhUGRyId4i09GlWQzU/iLCWDFUvb+cyRIhaAMocCet2U25dqwtTUFU9mcADR\n\tQUUvUcoLGY4QAlA==",
        "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": "The current implementation of AwbAlgorithm is the following one:\n\nAwbAlgorithm o---- RkISP1Awb\n    ^\n    |\n |-----|\n |     |\nBayes Grey\n\nThe platform-specific Awb implementation instantiate one of\nthe Bayes or Grey Awb helper classes and both inherit from the\nAwbAlgorithm base class.\n\nThe handling of libcamera controls is split between the base and the\nderived (mostly Bayes) class.\n\nWe want instead the AwbAlgorithm to handle all-things-libcamera and let\nthe Bayes and Grey classes implement the logic. In order to achieve that\nuse composition also in the AwbAlgorithm class and introduce an\nAwbImplementation base class from which Bayes and Grey derive from.\n\nAwbImplementation o---- AwbAlgorithmBase\n    ^                           ^\n    |                           |\n |-----|                AwbAlgorithm<Q<x,y>> o---- RkISP1Awb\n |     |\nBayes Grey\n\nThe AwbAlgorithm<> class is instantiated with a Q<x, y> template\nargument that represents the platform-specific register format which\nis used to initialize controls and clamp gains to the hardware limits.\n\nSigned-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\nSigned-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\nReviewed-by: Daniel Scally <dan.scally@ideasonboard.com>\nTested-by: Daniel Scally <dan.scally@ideasonboard.com>\n---\n src/ipa/libipa/awb.cpp            | 472 ++++++++++++++++++++++++++++++++------\n src/ipa/libipa/awb.h              | 109 +++++++--\n src/ipa/libipa/awb_bayes.cpp      |  52 ++---\n src/ipa/libipa/awb_bayes.h        |  12 +-\n src/ipa/libipa/awb_grey.cpp       |  25 +-\n src/ipa/libipa/awb_grey.h         |   6 +-\n src/ipa/rkisp1/algorithms/awb.cpp | 360 +++++++++--------------------\n src/ipa/rkisp1/algorithms/awb.h   |  18 +-\n src/ipa/rkisp1/ipa_context.h      |  29 +--\n 9 files changed, 672 insertions(+), 411 deletions(-)",
    "diff": "diff --git a/src/ipa/libipa/awb.cpp b/src/ipa/libipa/awb.cpp\nindex 29a78069c52c..95291840d8fb 100644\n--- a/src/ipa/libipa/awb.cpp\n+++ b/src/ipa/libipa/awb.cpp\n@@ -2,7 +2,7 @@\n /*\n  * Copyright (C) 2024 Ideas on Board Oy\n  *\n- * Generic AWB algorithms\n+ * libIPA Awb algorithms\n  */\n \n #include \"awb.h\"\n@@ -11,9 +11,16 @@\n \n #include <libcamera/control_ids.h>\n \n+#include \"awb_bayes.h\"\n+#include \"awb_grey.h\"\n+\n+constexpr int32_t kMinColourTemperature = 2500;\n+constexpr int32_t kMaxColourTemperature = 10000;\n+constexpr int32_t kDefaultColourTemperature = 5000;\n+\n /**\n  * \\file awb.h\n- * \\brief Base classes for AWB algorithms\n+ * \\brief libipa awb implementation\n  */\n \n namespace libcamera {\n@@ -22,30 +29,60 @@ LOG_DEFINE_CATEGORY(Awb)\n \n namespace ipa {\n \n+namespace awb {\n+\n /**\n- * \\class AwbResult\n- * \\brief The result of an AWB calculation\n+ * \\struct Context\n+ * \\brief Awb gains and colour temperature\n+ *\n+ * \\var Context::gains\n+ * \\brief The white balance gains\n  *\n- * This class holds the result of an auto white balance calculation.\n+ * \\var Context::temperatureK\n+ * \\brief The colour temperature, in Kelvin\n  */\n \n /**\n- * \\var AwbResult::gains\n- * \\brief The calculated white balance gains\n+ * \\struct ActiveState\n+ * \\brief Active awb state shared across frames\n+ *\n+ * \\var ActiveState::manual\n+ * \\brief The most recent manually requested awb state\n+ *\n+ * \\var ActiveState::automatic\n+ * \\brief The most recent automatically calculated awb state\n+ *\n+ * \\var ActiveState::autoEnabled\n+ * \\brief True when automatic awb is selected\n  */\n \n /**\n- * \\var AwbResult::colourTemperature\n- * \\brief The calculated colour temperature in Kelvin\n+ * \\struct FrameContext\n+ * \\brief Per-frame awb state\n+ *\n+ * \\var FrameContext::autoEnabled\n+ * \\brief True when automatic awb is in use\n  */\n \n+} /* namespace awb */\n+\n /**\n  * \\class AwbStats\n- * \\brief An abstraction class wrapping hardware-specific AWB statistics\n+ * \\brief An abstraction class wrapping hardware-specific awb statistics\n  *\n- * IPA modules using an AWB algorithm based on the AwbAlgorithm class need to\n- * implement this class to give the algorithm access to the hardware-specific\n- * statistics data.\n+ * IPA modules shall provide a derived implementation of this class to give the\n+ * awb algorithm access to the hardware-specific statistics data in a common\n+ * format.\n+ */\n+\n+/**\n+ * \\fn AwbStats::AwbStats()\n+ * \\brief Construct an empty AwbStat\n+ */\n+\n+/**\n+ * AwbStat::~AwbStat\n+ * \\brief Virtual class destructor\n  */\n \n /**\n@@ -57,7 +94,7 @@ namespace ipa {\n  * applied. To keep the actual implementations computationally inexpensive,\n  * the squared colour error shall be returned.\n  *\n- * If the AWB statistics provide multiple zones, the average of the individual\n+ * If the awb statistics provide multiple zones, the average of the individual\n  * squared errors shall be returned. Averaging/normalizing is necessary so that\n  * the numeric dimensions are the same on all hardware platforms.\n  *\n@@ -65,79 +102,328 @@ namespace ipa {\n  */\n \n /**\n- * \\fn AwbStats::rgbMeans()\n- * \\brief Get RGB means of the statistics\n+ * \\fn AwbStats::valid()\n+ * \\brief Retrieve if the awb statistics are valid\n  *\n- * Fetch the RGB means from the statistics. The values of each channel are\n- * dimensionless and only the ratios are used for further calculations. This is\n- * used by the simple grey world model to calculate the gains to apply.\n+ * If the colour mean values are too small, the AwbAlgorithm class doesn't have\n+ * enough information to meaningfully calculate white-balance gains. Freeze the\n+ * algorithm in that case.\n+ *\n+ * \\return True if the awb statistics are valid, false otherwise\n+ */\n+\n+/**\n+ * \\fn AwbStats::rgbMeans\n+ * \\brief Retrieve the RGB means values\n+ *\n+ * Fetch the RGB means from the statistics in the [0, 1] range.\n  *\n  * \\return The RGB means\n  */\n \n /**\n- * \\class AwbAlgorithm\n- * \\brief A base class for auto white balance algorithms\n+ * \\var AwbStats::rgbMeans_\n+ * \\brief Mean values of colour channels\n+ */\n+\n+/**\n+ * \\var AwbStats::rg_\n+ * \\brief Red/Green ratio\n+ */\n+\n+/**\n+ * \\var AwbStats::bg_\n+ * \\brief Blue/Green ratio\n+ */\n+\n+/**\n+ * \\class AwbImplementation\n+ * \\brief Pure virtual base class for awb algorithms implementations\n+ *\n+ * The AwbImplementation class defines the interface for the awb algorithm\n+ * implementations.\n  *\n- * This class is a base class for auto white balance algorithms. It defines an\n- * interface for the algorithms to implement, and is used by the IPAs to\n- * interact with the concrete implementation.\n+ * It is currently implemented by the AwbGrey and AwbBayes classes.\n+ *\n+ * The interface defines an init() function to initialize the algorithm with the\n+ * content of the tuning file and two functions to compute colour gains\n+ * according to the algorithm operating mode (auto or manual) in use.\n+ *\n+ * The calculateAwb() function calculates colour gains given a set of statistics\n+ * provided by the IPA module. It is used when the algorithm operates in auto\n+ * mode and gains are dynamically computed given a new set of statistics from\n+ * the awb engine.\n+ *\n+ * The gainsFromColourTemperature() function instead computes the white balance\n+ * gains from a colour temperature. This function does not take any statistics\n+ * into account. It is used to compute the colour gains when the user manually\n+ * specifies a colour temperature.\n  */\n \n /**\n- * \\fn AwbAlgorithm::init()\n- * \\brief Initialize the algorithm with the given tuning data\n- * \\param[in] tuningData The tuning data to use for the algorithm\n+ * \\struct AwbImplementation::Result\n+ * \\brief The result of an awb computation\n  *\n- * \\return 0 on success, a negative error code otherwise\n+ * \\var AwbImplementation::Result::gains\n+ * \\brief The computed white balance gains\n+ *\n+ * \\var AwbImplementation::Result::temperatureK\n+ * \\brief The computed colour temperature, in Kelvin\n  */\n \n /**\n- * \\fn AwbAlgorithm::calculateAwb()\n- * \\brief Calculate AWB data from the given statistics\n- * \\param[in] stats The statistics to use for the calculation\n- * \\param[in] lux The lux value of the scene\n+ * \\fn AwbImplementation::~AwbImplementation\n+ * \\brief Virtual class destructor\n+ */\n+\n+/**\n+ * \\fn AwbImplementation::init()\n+ * \\param[in] tuningData\n+ * \\brief Initialize the algorithm by parsing \\a tuningData\n+ */\n+\n+/**\n+ * \\fn AwbImplementation::calculateAwb()\n+ * \\param[in] stats The awb statistics\n+ * \\param[in] lux The estimated lux level\n+ * \\param[in] range The colour temperature search limits\n  *\n- * Calculate an AwbResult object from the given statistics and lux value. A \\a\n- * lux value of 0 means it is unknown or invalid and the algorithm shall ignore\n- * it.\n+ * Calculate a new set of colour gains and a colour temperature given a new\n+ * set of statistics \\a stats, an estimated luminance \\a lux and a range of\n+ * colour temperature to limit the search.\n  *\n- * \\return The AWB result\n+ * \\return A Result computed using the new statistics\n+ */\n+/**\n+ * \\fn AwbImplementation::gainsFromColourTemperature()\n+ * \\param[in] temperatureK Colour temperature in Kelvin\n+ *\n+ * Calculate a new set of colour gains for a new manually specified colour\n+ * temperature \\a temperatureK.\n+ *\n+ * \\return The RGB gain values adjusted to \\a temperatureK or std::nullopt\n+ */\n+\n+/**\n+ * \\class AwbAlgorithmBase\n+ * \\brief Base class for AwbAlgorithm for non-templated functions implementation\n+ *\n+ * Base class for AwbAlgorithm where non-templated functions are implemented.\n+ * IPA implementations shall use AwbAlgorithm and not this class.\n  */\n \n /**\n- * \\fn AwbAlgorithm::gainsFromColourTemperature()\n- * \\brief Compute white balance gains from a colour temperature\n- * \\param[in] colourTemperature The colour temperature in Kelvin\n+ * \\brief Initialize the algorithm with the given tuning data\n+ * \\param[in] tuningData The tuning data to use for the algorithm\n  *\n- * Compute the white balance gains from a \\a colourTemperature. This function\n- * does not take any statistics into account. It is used to compute the colour\n- * gains when the user manually specifies a colour temperature.\n+ * Parse \\a tuningData to initialize the awb algorithm and register controls.\n+ * IPA modules are expected to call this function as part of their\n+ * implementation of Algorithm::init().\n  *\n- * \\return The colour gains or std::nullopt if the conversion is not possible\n+ * \\return 0 on success, a negative error code otherwise\n+ */\n+int AwbAlgorithmBase::init(const ValueNode &tuningData)\n+{\n+\tbayes_ = false;\n+\n+\tif (!tuningData.contains(\"algorithm\"))\n+\t\tLOG(Awb, Info) << \"No Awb algorithm specified, using grey world\";\n+\n+\tauto mode = tuningData[\"algorithm\"].get<std::string>(\"grey\");\n+\tif (mode == \"grey\") {\n+\t\timpl_ = std::make_unique<AwbGrey>();\n+\t} else if (mode == \"bayes\") {\n+\t\timpl_ = std::make_unique<AwbBayes>();\n+\t\tbayes_ = true;\n+\t} else {\n+\t\tLOG(Awb, Error) << \"Unknown Awb algorithm: \" << mode;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tLOG(Awb, Debug) << \"Using Awb algorithm: \" << mode;\n+\n+\tint ret = impl_->init(tuningData);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tcontrols_[&controls::ColourTemperature] =\n+\t\tControlInfo(kMinColourTemperature, kMaxColourTemperature,\n+\t\t\t    kDefaultColourTemperature);\n+\tcontrols_[&controls::AwbEnable] = ControlInfo(false, true);\n+\n+\treturn parseModeConfigs(tuningData, controls::AwbAuto);\n+}\n+\n+/**\n+ * \\brief Configure the awb algorithm\n+ * \\param[in] state The awb active state\n+ * \\return 0 if successful, an error code otherwise\n  */\n+int AwbAlgorithmBase::configure(awb::ActiveState &state)\n+{\n+\tstate.manual.gains = RGB<double>{ 1.0 };\n+\tauto gains = impl_->gainsFromColourTemperature(kDefaultColourTemperature);\n+\tif (gains)\n+\t\tstate.automatic.gains = *gains;\n+\telse\n+\t\tstate.automatic.gains = RGB<double>{ 1.0 };\n+\n+\tstate.autoEnabled = true;\n+\tstate.manual.temperatureK = kDefaultColourTemperature;\n+\tstate.automatic.temperatureK = kDefaultColourTemperature;\n+\n+\treturn 0;\n+}\n \n /**\n- * \\fn AwbAlgorithm::controls()\n- * \\brief Get the controls info map for this algorithm\n+ * \\brief Queue a Request to the awb algorithm\n+ * \\param[in] state The awb active state\n+ * \\param[in] frame The frame number\n+ * \\param[in] frameContext The awb frame context\n+ * \\param[in] controls The list of controls part of the Request\n  *\n- * \\return The controls info map\n+ * Queue a new Request to the awb algorithm and modify its behaviour according\n+ * to the provided controls.\n+ *\n+ * The currently handled controls are:\n+ * - controls::AwbEnable\n+ * - controls::AwbMode\n+ * - controls::ColourGains\n+ * - controls::ColourTemperature\n  */\n+void AwbAlgorithmBase::queueRequest(awb::ActiveState &state,\n+\t\t\t\t    [[maybe_unused]] const uint32_t frame,\n+\t\t\t\t    awb::FrameContext &frameContext,\n+\t\t\t\t    const ControlList &controls)\n+{\n+\tconst auto &awbEnable = controls.get(controls::AwbEnable);\n+\tif (awbEnable && *awbEnable != state.autoEnabled) {\n+\t\tstate.autoEnabled = *awbEnable;\n+\n+\t\tLOG(Awb, Debug)\n+\t\t\t<< (*awbEnable ? \"Enabling\" : \"Disabling\") << \" Awb\";\n+\t}\n+\n+\tauto mode = controls.get(controls::AwbMode);\n+\tif (mode) {\n+\t\tauto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));\n+\t\tif (it == modes_.end()) {\n+\t\t\tLOG(Awb, Error) << \"Unsupported Awb mode \" << *mode;\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tcurrentMode_ = &it->second;\n+\t}\n+\n+\tframeContext.autoEnabled = state.autoEnabled;\n+\n+\tif (frameContext.autoEnabled)\n+\t\treturn;\n+\n+\tconst auto &colourGains = controls.get(controls::ColourGains);\n+\tconst auto &colourTemperature = controls.get(controls::ColourTemperature);\n+\tbool update = false;\n+\tif (colourGains) {\n+\t\tstate.manual.gains.r() = (*colourGains)[0];\n+\t\tstate.manual.gains.b() = (*colourGains)[1];\n+\t\t/*\n+\t\t * \\todo Colour temperature reported in metadata is now\n+\t\t * incorrect, as we can't deduce the temperature from the gains.\n+\t\t * This will be fixed with the bayes AWB algorithm.\n+\t\t */\n+\t\tupdate = true;\n+\t} else if (colourTemperature) {\n+\t\tstate.manual.temperatureK = *colourTemperature;\n+\t\tconst auto &gains = impl_->gainsFromColourTemperature(*colourTemperature);\n+\t\tif (gains) {\n+\t\t\tstate.manual.gains.r() = gains->r();\n+\t\t\tstate.manual.gains.b() = gains->b();\n+\t\t\tupdate = true;\n+\t\t}\n+\t}\n+\n+\tif (update)\n+\t\tLOG(Awb, Debug)\n+\t\t\t<< \"Set colour gains to \" << state.manual.gains;\n+\n+\tframeContext.gains = state.manual.gains;\n+\tframeContext.temperatureK = state.manual.temperatureK;\n+}\n \n /**\n- * \\fn AwbAlgorithm::handleControls()\n- * \\param[in] controls The controls to handle\n- * \\brief Handle the controls supplied in a request\n+ * \\brief Set the gains and colour temperature values in \\a frameContext\n+ * \\param[in] state The awb active state\n+ * \\param[in] frameContext The awb frame context\n+ *\n+ * If auto mode is enabled, take the most recently computed gains and use them\n+ * for the current frame. Otherwise, if in manual mode, gains and colour\n+ * temperature for a frame are set at queueRequest() time.\n  */\n+void AwbAlgorithmBase::prepare(awb::ActiveState &state,\n+\t\t\t       awb::FrameContext &frameContext)\n+{\n+\tif (frameContext.autoEnabled) {\n+\t\tframeContext.gains = state.automatic.gains;\n+\t\tframeContext.temperatureK = state.automatic.temperatureK;\n+\t}\n+}\n \n /**\n+ * \\brief Process awb statistics to calculate gains and populate metadata\n+ * \\param[in] state The awb active state\n+ * \\param[in] frameContext The awb frame context\n+ * \\param[in] stats The awb statistics\n+ * \\param[in] lux The lux value as estimated by the IPA module\n+ * \\param[out] metadata The metadata list\n+ *\n+ * Process \\a stats to calculate new gains and colour temperature and populate\n+ * \\a metadata with the results.\n+ */\n+void AwbAlgorithmBase::process(awb::ActiveState &state,\n+\t\t\t       awb::FrameContext &frameContext,\n+\t\t\t       const AwbStats &stats, unsigned int lux,\n+\t\t\t       ControlList &metadata)\n+{\n+\tif (!stats.valid())\n+\t\treturn;\n+\n+\tauto awbResult = impl_->calculateAwb(stats, lux, { currentMode_->ctLo,\n+\t\t\t\t\t\t\t   currentMode_->ctHi });\n+\n+\t/*\n+\t * Clamp the gain values to the hardware, according to the gainMin_\n+\t * and gainMax_ values.\n+\t */\n+\tawbResult.gains = awbResult.gains.clamp(gainMin_, gainMax_);\n+\n+\t/* Smooth color gains adjustments. */\n+\tdouble speed = 0.2;\n+\tdouble ct = awbResult.temperatureK;\n+\tct = ct * speed + state.automatic.temperatureK * (1 - speed);\n+\n+\tstate.automatic.temperatureK = awbResult.temperatureK;\n+\tstate.automatic.gains = awbResult.gains * speed +\n+\t\t\t\tstate.automatic.gains * (1 - speed);\n+\n+\t/* Populate metadata. */\n+\tmetadata.set(controls::AwbEnable, frameContext.autoEnabled);\n+\tmetadata.set(controls::ColourGains, { static_cast<float>(frameContext.gains.r()),\n+\t\t\t\t\t      static_cast<float>(frameContext.gains.b()) });\n+\tmetadata.set(controls::ColourTemperature, frameContext.temperatureK);\n+\n+\tLOG(Awb, Debug) << std::showpoint << \"Means \" << stats.rgbMeans()\n+\t\t\t<< \", gains \" << state.automatic.gains\n+\t\t\t<< \", temp \" << state.automatic.temperatureK << \"K\";\n+}\n+\n+/*\n  * \\brief Parse the mode configurations from the tuning data\n  * \\param[in] tuningData the ValueNode representing the tuning data\n  * \\param[in] def The default value for the AwbMode control\n  *\n  * Utility function to parse the tuning data for an AwbMode entry and read all\n- * provided modes. It adds controls::AwbMode to AwbAlgorithm::controls_ and\n- * populates AwbAlgorithm::modes_. For a list of possible modes see \\ref\n+ * provided modes. It adds controls::AwbMode to AwbAlgorithmBase::controls_ and\n+ * populates AwbAlgorithmBase::modes_. For a list of possible modes see \\ref\n  * controls::AwbModeEnum.\n  *\n  * Each mode entry must contain a \"lo\" and \"hi\" key to specify the lower and\n@@ -156,15 +442,23 @@ namespace ipa {\n  *       ...\n  * \\endcode\n  *\n- * If \\a def is supplied but not contained in the the \\a tuningData, -EINVAL is\n+ * If \\a def is supplied but not contained in the \\a tuningData, -EINVAL is\n  * returned.\n  *\n+ * AwbModes are only used by the AwbBayes implementation.\n+ *\n  * \\sa controls::AwbModeEnum\n  * \\return Zero on success, negative error code otherwise\n  */\n-int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,\n-\t\t\t\t   const ControlValue &def)\n+int AwbAlgorithmBase::parseModeConfigs(const ValueNode &tuningData,\n+\t\t\t\t       const ControlValue &def)\n {\n+\tif (!bayes_) {\n+\t\t/* AwbGrey does not support and does not use modes. */\n+\t\tcurrentMode_ = &AwbGreyMode;\n+\t\treturn 0;\n+\t}\n+\n \tstd::vector<ControlValue> availableModes;\n \n \tconst ValueNode &modes = tuningData[controls::AwbMode.name()];\n@@ -227,37 +521,83 @@ int AwbAlgorithm::parseModeConfigs(const ValueNode &tuningData,\n \t}\n \n \tcontrols_[&controls::AwbMode] = ControlInfo(availableModes, def);\n+\tcurrentMode_ = &modes_[controls::AwbAuto];\n \n \treturn 0;\n }\n \n /**\n- * \\class AwbAlgorithm::ModeConfig\n- * \\brief Holds the configuration of a single AWB mode\n+ * \\var AwbAlgorithmBase::controls_\n+ * \\brief Controls info map for the controls registered by the awb algorithm\n+ */\n+\n+/**\n+ * \\var AwbAlgorithmBase::gainMin_\n+ * \\brief The minimum supported gain value\n+ *\n+ * Minimum gain value used to clamp the awb algorithm calculation results in the\n+ * range supported by the platform awb engine.\n  *\n- * AWB modes limit the regulation of the AWB algorithm to a specific range of\n- * colour temperatures.\n+ * The min and max gain values are initialized by AwbAlgorithm::init().\n  */\n \n /**\n- * \\var AwbAlgorithm::ModeConfig::ctLo\n+ * \\var AwbAlgorithmBase::gainMax_\n+ * \\brief The maximum supported gain value\n+ *\n+ * Maximum gain value used to clamp the awb algorithm calculation results in the\n+ * range supported by the platform awb engine.\n+ *\n+ * The min and max gain values are initialized by AwbAlgorithm::init().\n+ */\n+\n+/*\n+ * \\class AwbAlgorithmBase::ModeConfig\n+ * \\brief Holds the configuration of a single awb mode\n+ *\n+ * AWB modes limit the regulation of the awb algorithm to a specific range of\n+ * colour temperatures. Use by AwbBayes only.\n+ */\n+\n+/*\n+ * \\var AwbAlgorithmBase::ModeConfig::ctLo\n  * \\brief The lowest valid colour temperature of that mode\n  */\n \n-/**\n- * \\var AwbAlgorithm::ModeConfig::ctHi\n+/*\n+ * \\var AwbAlgorithmBase::ModeConfig::ctHi\n  * \\brief The highest valid colour temperature of that mode\n  */\n \n+/*\n+ * \\var AwbAlgorithmBase::modes_\n+ * \\brief Map of all configured modes\n+ * \\sa AwbAlgorithmBase::parseModeConfigs\n+ */\n+\n /**\n- * \\var AwbAlgorithm::controls_\n- * \\brief Controls info map for the controls provided by the algorithm\n+ * \\class AwbAlgorithm\n+ * \\brief The libipa awb algorithm\n+ * \\tparam Q The fixedpoint register representation of gain values\n+ *\n+ * Implement the awb algorithm for libipa.\n+ *\n+ * The AwbAlgorithm class implements an interface similar in spirit to the one\n+ * of the Algorithm class. IPA modules are expected to store an instance of\n+ * AwbAlgorithm as class member, template it with the awb engine gain register\n+ * representation and call its function in their implementations of the\n+ * Algorithm interface.\n+ *\n+ * The AwbAlgorithm instantiates an AwbImplementation implementation (AwbGrey or\n+ * AwbBayes) at init() time by parsing the tuning data and uses it to compute\n+ * the RGB gains and estimate a colour temperature given a set of statistics\n+ * in the form of a AwbStats derived implementation.\n  */\n \n /**\n- * \\var AwbAlgorithm::modes_\n- * \\brief Map of all configured modes\n- * \\sa AwbAlgorithm::parseModeConfigs\n+ * \\fn AwbAlgorithm::init()\n+ * \\param[in] controls The info map of the IPA controls\n+ * \\copydoc AwbAlgorithmBase::init()\n  */\n \n } /* namespace ipa */\ndiff --git a/src/ipa/libipa/awb.h b/src/ipa/libipa/awb.h\nindex 09c00e47d604..72f46367a136 100644\n--- a/src/ipa/libipa/awb.h\n+++ b/src/ipa/libipa/awb.h\n@@ -2,11 +2,12 @@\n /*\n  * Copyright (C) 2024 Ideas on Board Oy\n  *\n- * Generic AWB algorithms\n+ * libIPA AWB algorithms\n  */\n \n #pragma once\n \n+#include <array>\n #include <map>\n #include <optional>\n \n@@ -20,46 +21,118 @@ namespace libcamera {\n \n namespace ipa {\n \n-struct AwbResult {\n+namespace awb {\n+\n+struct Context {\n \tRGB<double> gains;\n-\tdouble colourTemperature;\n+\tunsigned int temperatureK;\n };\n \n+struct ActiveState {\n+\tContext manual;\n+\tContext automatic;\n+\n+\tbool autoEnabled;\n+};\n+\n+struct FrameContext : public Context {\n+\tbool autoEnabled;\n+};\n+\n+} /* namespace awb */\n+\n struct AwbStats {\n+\tAwbStats() = default;\n+\tvirtual ~AwbStats() = default;\n+\n \tvirtual double computeColourError(const RGB<double> &gains) const = 0;\n-\tvirtual RGB<double> rgbMeans() const = 0;\n+\tvirtual bool valid() const = 0;\n+\tRGB<double> rgbMeans() const { return rgbMeans_; }\n \n protected:\n-\t~AwbStats() = default;\n+\tRGB<double> rgbMeans_;\n+\tdouble rg_;\n+\tdouble bg_;\n };\n \n-class AwbAlgorithm\n+class AwbImplementation\n {\n public:\n-\tvirtual ~AwbAlgorithm() = default;\n+\tstruct Result {\n+\t\tRGB<double> gains;\n+\t\tunsigned int temperatureK;\n+\t};\n \n+\tvirtual ~AwbImplementation() = default;\n \tvirtual int init(const ValueNode &tuningData) = 0;\n-\tvirtual AwbResult calculateAwb(const AwbStats &stats, unsigned int lux) = 0;\n-\tvirtual std::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) = 0;\n+\tvirtual Result calculateAwb(const AwbStats &stats, unsigned int lux,\n+\t\t\t\t    std::array<double, 2> range) = 0;\n+\tvirtual std::optional<RGB<double>>\n+\tgainsFromColourTemperature(double temperatureK) = 0;\n+};\n \n-\tconst ControlInfoMap::Map &controls() const\n-\t{\n-\t\treturn controls_;\n-\t}\n+class AwbAlgorithmBase\n+{\n+public:\n+\tint init(const ValueNode &tuningData);\n \n-\tvirtual void handleControls([[maybe_unused]] const ControlList &controls) {}\n+\tint configure(awb::ActiveState &state);\n+\n+\tvoid queueRequest(awb::ActiveState &state,\n+\t\t\t  const uint32_t frame,\n+\t\t\t  awb::FrameContext &frameContext,\n+\t\t\t  const ControlList &controls);\n+\n+\tvoid prepare(awb::ActiveState &state, awb::FrameContext &frameContext);\n+\n+\tvoid process(awb::ActiveState &state, awb::FrameContext &frameContext,\n+\t\t     const AwbStats &stats, unsigned int lux,\n+\t\t     ControlList &metadata);\n \n protected:\n-\tint parseModeConfigs(const ValueNode &tuningData,\n-\t\t\t     const ControlValue &def = {});\n+\tAwbAlgorithmBase() = default;\n+\n+\tControlInfoMap::Map controls_;\n+\tfloat gainMin_;\n+\tfloat gainMax_;\n \n+private:\n \tstruct ModeConfig {\n \t\tdouble ctHi;\n \t\tdouble ctLo;\n \t};\n \n-\tControlInfoMap::Map controls_;\n-\tstd::map<controls::AwbModeEnum, AwbAlgorithm::ModeConfig> modes_;\n+\t/* AwbGrey does not support modes; */\n+\tstatic constexpr ModeConfig AwbGreyMode = { 0.0, 0.0 };\n+\n+\tint parseModeConfigs(const ValueNode &tuningData,\n+\t\t\t     const ControlValue &def = {});\n+\n+\tstd::map<controls::AwbModeEnum, AwbAlgorithmBase::ModeConfig> modes_;\n+\tconst ModeConfig *currentMode_ = nullptr;\n+\tstd::unique_ptr<AwbImplementation> impl_;\n+\tbool bayes_ = false;\n+};\n+\n+template<typename Q>\n+class AwbAlgorithm : public AwbAlgorithmBase\n+{\n+public:\n+\tint init(const ValueNode &tuningData, ControlInfoMap::Map &controls)\n+\t{\n+\t\tAwbAlgorithmBase::init(tuningData);\n+\n+\t\tgainMin_ = std::max(Q::TraitsType::min, 1.0f);\n+\t\tgainMax_ = Q::TraitsType::max;\n+\n+\t\tcontrols_[&controls::ColourGains] =\n+\t\t\tControlInfo(gainMin_, gainMax_,\n+\t\t\t\t    Span<const float, 2>{ { 1.0f, 1.0f } });\n+\n+\t\tcontrols.insert(controls_.begin(), controls_.end());\n+\n+\t\treturn 0;\n+\t}\n };\n \n } /* namespace ipa */\ndiff --git a/src/ipa/libipa/awb_bayes.cpp b/src/ipa/libipa/awb_bayes.cpp\nindex 9fd85e5a4505..2c10f9c4c82d 100644\n--- a/src/ipa/libipa/awb_bayes.cpp\n+++ b/src/ipa/libipa/awb_bayes.cpp\n@@ -140,11 +140,6 @@ void Interpolator<Pwl>::interpolate(const Pwl &a, const Pwl &b, Pwl &dest, doubl\n  * \\brief How far to wander off CT curve towards \"more green\"\n  */\n \n-/**\n- * \\var AwbBayes::currentMode_\n- * \\brief The currently selected mode\n- */\n-\n int AwbBayes::init(const ValueNode &tuningData)\n {\n \tint ret = colourGainCurve_.readYaml(tuningData[\"colourGains\"], \"ct\", \"gains\");\n@@ -172,14 +167,6 @@ int AwbBayes::init(const ValueNode &tuningData)\n \t\treturn ret;\n \t}\n \n-\tret = parseModeConfigs(tuningData, controls::AwbAuto);\n-\tif (ret) {\n-\t\tLOG(Awb, Error)\n-\t\t\t<< \"Failed to parse mode parameter from tuning file\";\n-\t\treturn ret;\n-\t}\n-\tcurrentMode_ = &modes_[controls::AwbAuto];\n-\n \ttransversePos_ = tuningData[\"transversePos\"].get<double>(0.01);\n \ttransverseNeg_ = tuningData[\"transverseNeg\"].get<double>(0.01);\n \tif (transversePos_ <= 0 || transverseNeg_ <= 0) {\n@@ -260,18 +247,16 @@ int AwbBayes::readPriors(const ValueNode &tuningData)\n \treturn 0;\n }\n \n-void AwbBayes::handleControls(const ControlList &controls)\n-{\n-\tauto mode = controls.get(controls::AwbMode);\n-\tif (mode) {\n-\t\tauto it = modes_.find(static_cast<controls::AwbModeEnum>(*mode));\n-\t\tif (it != modes_.end())\n-\t\t\tcurrentMode_ = &it->second;\n-\t\telse\n-\t\t\tLOG(Awb, Error) << \"Unsupported AWB mode \" << *mode;\n-\t}\n-}\n-\n+/**\n+ * \\brief Compute white balance gains from a colour temperature\n+ * \\param[in] colourTemperature The colour temperature in Kelvin\n+ *\n+ * Compute the white balance gains from a \\a colourTemperature. This function\n+ * does not take any statistics into account. It simply interpolates the colour\n+ * gains curve on a new colour temperature.\n+ *\n+ * \\return The RGB gain values adjusted to \\a temperatureK\n+ */\n std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTemperature)\n {\n \t/*\n@@ -283,7 +268,9 @@ std::optional<RGB<double>> AwbBayes::gainsFromColourTemperature(double colourTem\n \treturn RGB<double>{ { gains[0], 1.0, gains[1] } };\n }\n \n-AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)\n+AwbImplementation::Result\n+AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux,\n+\t\t       std::array<double, 2> range)\n {\n \tipa::Pwl prior;\n \tif (lux > 0) {\n@@ -295,7 +282,7 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)\n \t\tprior.append(0, 1.0);\n \t}\n \n-\tdouble t = coarseSearch(prior, stats);\n+\tdouble t = coarseSearch(prior, stats, range);\n \tdouble r = ctR_.eval(t);\n \tdouble b = ctB_.eval(t);\n \tLOG(Awb, Debug)\n@@ -316,14 +303,15 @@ AwbResult AwbBayes::calculateAwb(const AwbStats &stats, unsigned int lux)\n \t\t<< \"After fine search: r \" << r << \" b \" << b << \" (gains r \"\n \t\t<< 1 / r << \" b \" << 1 / b << \")\";\n \n-\treturn { { { 1.0 / r, 1.0, 1.0 / b } }, t };\n+\treturn { { { 1.0 / r, 1.0, 1.0 / b } }, static_cast<unsigned int>(t) };\n }\n \n-double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const\n+double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,\n+\t\t\t      Span<double> range) const\n {\n \tstd::vector<Pwl::Point> points;\n \tsize_t bestPoint = 0;\n-\tdouble t = currentMode_->ctLo;\n+\tdouble t = range[0];\n \tint spanR = -1;\n \tint spanB = -1;\n \tLimitsRecorder<double> errorLimits;\n@@ -345,14 +333,14 @@ double AwbBayes::coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) cons\n \t\tif (points.back().y() < points[bestPoint].y())\n \t\t\tbestPoint = points.size() - 1;\n \n-\t\tif (t == currentMode_->ctHi)\n+\t\tif (t == range[1])\n \t\t\tbreak;\n \n \t\t/*\n \t\t * Ensure even steps along the r/b curve by scaling them by the\n \t\t * current t.\n \t\t */\n-\t\tt = std::min(t + t / 10 * kSearchStep, currentMode_->ctHi);\n+\t\tt = std::min(t + t / 10 * kSearchStep, range[1]);\n \t}\n \n \tt = points[bestPoint].x();\ndiff --git a/src/ipa/libipa/awb_bayes.h b/src/ipa/libipa/awb_bayes.h\nindex 1e3373676bc0..8d4cff076d4a 100644\n--- a/src/ipa/libipa/awb_bayes.h\n+++ b/src/ipa/libipa/awb_bayes.h\n@@ -20,22 +20,24 @@ namespace libcamera {\n \n namespace ipa {\n \n-class AwbBayes : public AwbAlgorithm\n+class AwbBayes : public AwbImplementation\n {\n public:\n \tAwbBayes() = default;\n \n \tint init(const ValueNode &tuningData) override;\n-\tAwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;\n+\tAwbImplementation::Result\n+\tcalculateAwb(const AwbStats &stats, unsigned int lux,\n+\t\t     std::array<double, 2> range) override;\n \tstd::optional<RGB<double>> gainsFromColourTemperature(double temperatureK) override;\n-\tvoid handleControls(const ControlList &controls) override;\n \n private:\n \tint readPriors(const ValueNode &tuningData);\n \n \tvoid fineSearch(double &t, double &r, double &b, ipa::Pwl const &prior,\n \t\t\tconst AwbStats &stats) const;\n-\tdouble coarseSearch(const ipa::Pwl &prior, const AwbStats &stats) const;\n+\tdouble coarseSearch(const ipa::Pwl &prior, const AwbStats &stats,\n+\t\t\t    Span<double> range) const;\n \tdouble interpolateQuadratic(ipa::Pwl::Point const &a,\n \t\t\t\t    ipa::Pwl::Point const &b,\n \t\t\t\t    ipa::Pwl::Point const &c) const;\n@@ -50,8 +52,6 @@ private:\n \n \tdouble transversePos_;\n \tdouble transverseNeg_;\n-\n-\tModeConfig *currentMode_ = nullptr;\n };\n \n } /* namespace ipa */\ndiff --git a/src/ipa/libipa/awb_grey.cpp b/src/ipa/libipa/awb_grey.cpp\nindex b14b096810ae..1e4af717f62d 100644\n--- a/src/ipa/libipa/awb_grey.cpp\n+++ b/src/ipa/libipa/awb_grey.cpp\n@@ -60,6 +60,7 @@ int AwbGrey::init(const ValueNode &tuningData)\n  * \\brief Calculate AWB data from the given statistics\n  * \\param[in] stats The statistics to use for the calculation\n  * \\param[in] lux The lux value of the scene\n+ * \\param[in] range The colour temperature search limits\n  *\n  * The colour temperature is estimated based on the colours::estimateCCT()\n  * function. The gains are calculated purely based on the RGB means provided by\n@@ -70,20 +71,23 @@ int AwbGrey::init(const ValueNode &tuningData)\n  *\n  * \\return The AWB result\n  */\n-AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux)\n+AwbImplementation::Result\n+AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned int lux,\n+\t\t      [[maybe_unused]] std::array<double, 2> range)\n {\n-\tAwbResult result;\n+\tAwbImplementation::Result result;\n \tauto means = stats.rgbMeans();\n-\tresult.colourTemperature = estimateCCT(means);\n+\tresult.temperatureK = estimateCCT(means);\n \n \t/*\n-\t * Estimate the red and blue gains to apply in a grey world. The green\n-\t * gain is hardcoded to 1.0. Avoid divisions by zero by clamping the\n-\t * divisor to a minimum value of 1.0.\n+\t * Calculate the red and blue gains to apply in a grey world by simply\n+\t * inverting the red/green and blue/green ratios as reported in\n+\t * statistics. The green gain is hardcoded to 1.0.\n \t */\n \tresult.gains.r() = means.g() / std::max(means.r(), 1.0);\n \tresult.gains.g() = 1.0;\n \tresult.gains.b() = means.g() / std::max(means.b(), 1.0);\n+\n \treturn result;\n }\n \n@@ -93,15 +97,16 @@ AwbResult AwbGrey::calculateAwb(const AwbStats &stats, [[maybe_unused]] unsigned\n  *\n  * Compute the white balance gains from a \\a colourTemperature. This function\n  * does not take any statistics into account. It simply interpolates the colour\n- * gains configured in the colour temperature curve.\n+ * gains curve (if provided in the tuning file) on a new colour temperature.\n  *\n- * \\return The colour gains if a colour temperature curve is available,\n- * [1, 1, 1] otherwise.\n+ * \\return The RGB gain values adjusted to \\a temperatureK or std::nullopt if\n+ * no gain curve is specified in the tuning data\n  */\n std::optional<RGB<double>> AwbGrey::gainsFromColourTemperature(double colourTemperature)\n {\n \tif (!colourGainCurve_) {\n-\t\tLOG(Awb, Error) << \"No gains defined\";\n+\t\tLOG(Awb, Info) << \"No gains curve defined, \"\n+\t\t\t       << \"unable to interpolate gains to colour temperature\";\n \t\treturn std::nullopt;\n \t}\n \ndiff --git a/src/ipa/libipa/awb_grey.h b/src/ipa/libipa/awb_grey.h\nindex 154a2af97f15..d1b0088f0470 100644\n--- a/src/ipa/libipa/awb_grey.h\n+++ b/src/ipa/libipa/awb_grey.h\n@@ -18,13 +18,15 @@ namespace libcamera {\n \n namespace ipa {\n \n-class AwbGrey : public AwbAlgorithm\n+class AwbGrey : public AwbImplementation\n {\n public:\n \tAwbGrey() = default;\n \n \tint init(const ValueNode &tuningData) override;\n-\tAwbResult calculateAwb(const AwbStats &stats, unsigned int lux) override;\n+\tAwbImplementation::Result\n+\tcalculateAwb(const AwbStats &stats, unsigned int lux,\n+\t\t     std::array<double, 2> range) override;\n \tstd::optional<RGB<double>> gainsFromColourTemperature(double colourTemperature) override;\n \n private:\ndiff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp\nindex 0eb05c2b6e1a..ae9f6a0c243e 100644\n--- a/src/ipa/rkisp1/algorithms/awb.cpp\n+++ b/src/ipa/rkisp1/algorithms/awb.cpp\n@@ -8,17 +8,12 @@\n #include \"awb.h\"\n \n #include <algorithm>\n-#include <ios>\n \n #include <libcamera/base/log.h>\n \n-#include <libcamera/control_ids.h>\n-\n #include <libcamera/ipa/core_ipa_interface.h>\n \n-#include \"libipa/awb_bayes.h\"\n-#include \"libipa/awb_grey.h\"\n-#include \"libipa/colours.h\"\n+#include \"libcamera/internal/vector.h\"\n \n /**\n  * \\file awb.h\n@@ -28,26 +23,16 @@ namespace libcamera {\n \n namespace ipa::rkisp1::algorithms {\n \n-/**\n- * \\class Awb\n- * \\brief Manage the white balance with automatic and manual controls\n- */\n-\n LOG_DEFINE_CATEGORY(RkISP1Awb)\n \n-constexpr int32_t kMinColourTemperature = 2500;\n-constexpr int32_t kMaxColourTemperature = 10000;\n-constexpr int32_t kDefaultColourTemperature = 5000;\n-\n-/* Minimum mean value below which AWB can't operate. */\n-constexpr double kMeanMinThreshold = 2.0;\n-\n class RkISP1AwbStats final : public AwbStats\n {\n public:\n-\tRkISP1AwbStats(const RGB<double> &rgbMeans)\n-\t\t: rgbMeans_(rgbMeans)\n+\tRkISP1AwbStats(const IPAFrameContext &frameContext,\n+\t\t       const rkisp1_stat_buffer *stats)\n \t{\n+\t\tcalculateRgbMeans(frameContext, stats);\n+\n \t\trg_ = rgbMeans_.r() / rgbMeans_.g();\n \t\tbg_ = rgbMeans_.b() / rgbMeans_.g();\n \t}\n@@ -65,17 +50,106 @@ public:\n \t\treturn delta2;\n \t}\n \n-\tRGB<double> rgbMeans() const override\n+\tbool valid() const override\n \t{\n-\t\treturn rgbMeans_;\n+\t\t/* Minimum mean value below which AWB can't operate. */\n+\t\tconstexpr double minValue = 2.0;\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+\tvoid calculateRgbMeans(const IPAFrameContext &frameContext,\n+\t\t\t       const rkisp1_stat_buffer *stats);\n+\tbool rgbMode_ = false;\n };\n \n+void RkISP1AwbStats::calculateRgbMeans(const IPAFrameContext &frameContext,\n+\t\t\t\t       const rkisp1_stat_buffer *stats)\n+{\n+\tif (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {\n+\t\tLOG(RkISP1Awb, Error) << \"AWB data is missing in statistics\";\n+\t\treturn;\n+\t}\n+\n+\tconst rkisp1_cif_isp_awb_stat *awb = &stats->params.awb;\n+\n+\tif (awb->awb_mean[0].cnt == 0) {\n+\t\tLOG(RkISP1Awb, Debug) << \"AWB statistics are empty\";\n+\t\treturn;\n+\t}\n+\n+\tif (rgbMode_) {\n+\t\trgbMeans_ = {{\n+\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_cr_or_r),\n+\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_y_or_g),\n+\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_cb_or_b)\n+\t\t}};\n+\t} else {\n+\t\t/* Get the YCbCr mean values */\n+\t\tVector<double, 3> yuvMeans({\n+\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_y_or_g),\n+\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_cb_or_b),\n+\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_cr_or_r)\n+\t\t});\n+\n+\t\t/*\n+\t\t * Convert from YCbCr to RGB. The hardware uses the following\n+\t\t * formulas:\n+\t\t *\n+\t\t * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B\n+\t\t * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B\n+\t\t * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B\n+\t\t *\n+\t\t * This seems to be based on limited range BT.601 with Q1.6\n+\t\t * precision.\n+\t\t *\n+\t\t * The inverse matrix is:\n+\t\t *\n+\t\t * [[1,1636, -0,0623,  1,6008]\n+\t\t *  [1,1636, -0,4045, -0,7949]\n+\t\t *  [1,1636,  1,9912, -0,0250]]\n+\t\t */\n+\t\tstatic const Matrix<double, 3, 3> yuv2rgbMatrix({\n+\t\t\t1.1636, -0.0623,  1.6008,\n+\t\t\t1.1636, -0.4045, -0.7949,\n+\t\t\t1.1636,  1.9912, -0.0250\n+\t\t});\n+\t\tstatic const Vector<double, 3> yuv2rgbOffset({\n+\t\t\t16, 128, 128\n+\t\t});\n+\n+\t\trgbMeans_ = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);\n+\n+\t\t/*\n+\t\t * Due to hardware rounding errors in the YCbCr means, the\n+\t\t * calculated RGB means may be negative. This would lead to\n+\t\t * negative gains, messing up calculation. Prevent this by\n+\t\t * clamping the means to positive values.\n+\t\t */\n+\t\trgbMeans_ = rgbMeans_.max(0.0);\n+\t}\n+\n+\t/*\n+\t * The ISP computes the AWB means after applying the CCM. Apply the\n+\t * inverse as we want to get the raw means before the colour gains.\n+\t */\n+\trgbMeans_ = frameContext.ccm.ccm.inverse() * rgbMeans_;\n+\n+\t/*\n+\t * The ISP computes the AWB means after applying the colour gains,\n+\t * divide by the gains that were used to get the raw means from the\n+\t * sensor. Apply a minimum value to avoid divisions by near-zero.\n+\t */\n+\trgbMeans_ /= frameContext.awb.gains.max(0.01);\n+}\n+\n+/**\n+ * \\class Awb\n+ * \\brief Manage the white balance with automatic and manual controls\n+ */\n+\n Awb::Awb()\n \t: rgbMode_(false)\n {\n@@ -86,40 +160,7 @@ Awb::Awb()\n  */\n int Awb::init(IPAContext &context, const ValueNode &tuningData)\n {\n-\tauto &cmap = context.ctrlMap;\n-\tcmap[&controls::ColourTemperature] = ControlInfo(kMinColourTemperature,\n-\t\t\t\t\t\t\t kMaxColourTemperature,\n-\t\t\t\t\t\t\t kDefaultColourTemperature);\n-\tcmap[&controls::AwbEnable] = ControlInfo(false, true);\n-\n-\tcmap[&controls::ColourGains] = ControlInfo(0.0f, 3.996f,\n-\t\t\t\t\t\t   Span<const float, 2>{ { 1.0f, 1.0f } });\n-\n-\tif (!tuningData.contains(\"algorithm\"))\n-\t\tLOG(RkISP1Awb, Info) << \"No AWB algorithm specified.\"\n-\t\t\t\t     << \" Default to grey world\";\n-\n-\tauto mode = tuningData[\"algorithm\"].get<std::string>(\"grey\");\n-\tif (mode == \"grey\") {\n-\t\tawbAlgo_ = std::make_unique<AwbGrey>();\n-\t} else if (mode == \"bayes\") {\n-\t\tawbAlgo_ = std::make_unique<AwbBayes>();\n-\t} else {\n-\t\tLOG(RkISP1Awb, Error) << \"Unknown AWB algorithm: \" << mode;\n-\t\treturn -EINVAL;\n-\t}\n-\tLOG(RkISP1Awb, Debug) << \"Using AWB algorithm: \" << mode;\n-\n-\tint ret = awbAlgo_->init(tuningData);\n-\tif (ret) {\n-\t\tLOG(RkISP1Awb, Error) << \"Failed to init AWB algorithm\";\n-\t\treturn ret;\n-\t}\n-\n-\tconst auto &src = awbAlgo_->controls();\n-\tcmap.insert(src.begin(), src.end());\n-\n-\treturn 0;\n+\treturn awbAlgo_.init(tuningData, context.ctrlMap);\n }\n \n /**\n@@ -128,16 +169,7 @@ int Awb::init(IPAContext &context, const ValueNode &tuningData)\n int Awb::configure(IPAContext &context,\n \t\t   const IPACameraSensorInfo &configInfo)\n {\n-\tcontext.activeState.awb.manual.gains = RGB<double>{ 1.0 };\n-\tauto gains = awbAlgo_->gainsFromColourTemperature(kDefaultColourTemperature);\n-\tif (gains)\n-\t\tcontext.activeState.awb.automatic.gains = *gains;\n-\telse\n-\t\tcontext.activeState.awb.automatic.gains = RGB<double>{ 1.0 };\n-\n-\tcontext.activeState.awb.autoEnabled = true;\n-\tcontext.activeState.awb.manual.temperatureK = kDefaultColourTemperature;\n-\tcontext.activeState.awb.automatic.temperatureK = kDefaultColourTemperature;\n+\tawbAlgo_.configure(context.activeState.awb);\n \n \t/*\n \t * Define the measurement window for AWB as a centered rectangle\n@@ -156,56 +188,12 @@ int Awb::configure(IPAContext &context,\n /**\n  * \\copydoc libcamera::ipa::Algorithm::queueRequest\n  */\n-void Awb::queueRequest(IPAContext &context,\n-\t\t       [[maybe_unused]] const uint32_t frame,\n+void Awb::queueRequest(IPAContext &context, const uint32_t frame,\n \t\t       IPAFrameContext &frameContext,\n \t\t       const ControlList &controls)\n {\n-\tauto &awb = context.activeState.awb;\n-\n-\tconst auto &awbEnable = controls.get(controls::AwbEnable);\n-\tif (awbEnable && *awbEnable != awb.autoEnabled) {\n-\t\tawb.autoEnabled = *awbEnable;\n-\n-\t\tLOG(RkISP1Awb, Debug)\n-\t\t\t<< (*awbEnable ? \"Enabling\" : \"Disabling\") << \" AWB\";\n-\t}\n-\n-\tawbAlgo_->handleControls(controls);\n-\n-\tframeContext.awb.autoEnabled = awb.autoEnabled;\n-\n-\tif (awb.autoEnabled)\n-\t\treturn;\n-\n-\tconst auto &colourGains = controls.get(controls::ColourGains);\n-\tconst auto &colourTemperature = controls.get(controls::ColourTemperature);\n-\tbool update = false;\n-\tif (colourGains) {\n-\t\tawb.manual.gains.r() = (*colourGains)[0];\n-\t\tawb.manual.gains.b() = (*colourGains)[1];\n-\t\t/*\n-\t\t * \\todo Colour temperature reported in metadata is now\n-\t\t * incorrect, as we can't deduce the temperature from the gains.\n-\t\t * This will be fixed with the bayes AWB algorithm.\n-\t\t */\n-\t\tupdate = true;\n-\t} else if (colourTemperature) {\n-\t\tawb.manual.temperatureK = *colourTemperature;\n-\t\tconst auto &gains = awbAlgo_->gainsFromColourTemperature(*colourTemperature);\n-\t\tif (gains) {\n-\t\t\tawb.manual.gains.r() = gains->r();\n-\t\t\tawb.manual.gains.b() = gains->b();\n-\t\t\tupdate = true;\n-\t\t}\n-\t}\n-\n-\tif (update)\n-\t\tLOG(RkISP1Awb, Debug)\n-\t\t\t<< \"Set colour gains to \" << awb.manual.gains;\n-\n-\tframeContext.awb.gains = awb.manual.gains;\n-\tframeContext.awb.temperatureK = awb.manual.temperatureK;\n+\tawbAlgo_.queueRequest(context.activeState.awb, frame, frameContext.awb,\n+\t\t\t      controls);\n }\n \n /**\n@@ -214,15 +202,7 @@ void Awb::queueRequest(IPAContext &context,\n void Awb::prepare(IPAContext &context, const uint32_t frame,\n \t\t  IPAFrameContext &frameContext, RkISP1Params *params)\n {\n-\t/*\n-\t * This is the latest time we can read the active state. This is the\n-\t * most up-to-date automatic values we can read.\n-\t */\n-\tif (frameContext.awb.autoEnabled) {\n-\t\tconst auto &awb = context.activeState.awb;\n-\t\tframeContext.awb.gains = awb.automatic.gains;\n-\t\tframeContext.awb.temperatureK = awb.automatic.temperatureK;\n-\t}\n+\tawbAlgo_.prepare(context.activeState.awb, frameContext.awb);\n \n \tauto gainConfig = params->block<BlockType::AwbGain>();\n \tgainConfig.setEnabled(true);\n@@ -291,136 +271,12 @@ void Awb::process(IPAContext &context,\n \t\t  const rkisp1_stat_buffer *stats,\n \t\t  ControlList &metadata)\n {\n-\tIPAActiveState &activeState = context.activeState;\n-\n-\tmetadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);\n-\tmetadata.set(controls::ColourGains, {\n-\t\t\tstatic_cast<float>(frameContext.awb.gains.r()),\n-\t\t\tstatic_cast<float>(frameContext.awb.gains.b())\n-\t\t});\n-\tmetadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);\n-\n-\tif (!stats || !(stats->meas_type & RKISP1_CIF_ISP_STAT_AWB)) {\n-\t\tLOG(RkISP1Awb, Error) << \"AWB data is missing in statistics\";\n-\t\treturn;\n-\t}\n-\n-\tconst rkisp1_cif_isp_stat *params = &stats->params;\n-\tconst rkisp1_cif_isp_awb_stat *awb = &params->awb;\n+\tRkISP1AwbStats awbStats(frameContext, stats);\n \n-\tif (awb->awb_mean[0].cnt == 0) {\n-\t\tLOG(RkISP1Awb, Debug) << \"AWB statistics are empty\";\n-\t\treturn;\n-\t}\n-\n-\tRGB<double> rgbMeans = calculateRgbMeans(frameContext, awb);\n-\n-\t/*\n-\t * If the means are too small we don't have enough information to\n-\t * meaningfully calculate gains. Freeze the algorithm in that case.\n-\t */\n-\tif (rgbMeans.r() < kMeanMinThreshold && rgbMeans.g() < kMeanMinThreshold &&\n-\t    rgbMeans.b() < kMeanMinThreshold)\n-\t\treturn;\n-\n-\tRkISP1AwbStats awbStats{ rgbMeans };\n-\tAwbResult awbResult = awbAlgo_->calculateAwb(awbStats, frameContext.lux.lux);\n-\n-\t/*\n-\t * Clamp the gain values to the hardware, which expresses gains as Q2.8\n-\t * unsigned integer values. Set the minimum just above zero to avoid\n-\t * divisions by zero when computing the raw means in subsequent\n-\t * iterations.\n-\t */\n-\tawbResult.gains = awbResult.gains.clamp(1.0 / 256, 1023.0 / 256);\n-\n-\t/* Filter the values to avoid oscillations. */\n-\tdouble speed = 0.2;\n-\tdouble ct = awbResult.colourTemperature;\n-\tct = ct * speed + activeState.awb.automatic.temperatureK * (1 - speed);\n-\tawbResult.gains = awbResult.gains * speed +\n-\t\t\t  activeState.awb.automatic.gains * (1 - speed);\n-\n-\tactiveState.awb.automatic.temperatureK = static_cast<unsigned int>(ct);\n-\tactiveState.awb.automatic.gains = awbResult.gains;\n-\n-\tLOG(RkISP1Awb, Debug)\n-\t\t<< std::showpoint\n-\t\t<< \"Means \" << rgbMeans << \", gains \"\n-\t\t<< activeState.awb.automatic.gains << \", temp \"\n-\t\t<< activeState.awb.automatic.temperatureK << \"K\";\n+\tawbAlgo_.process(context.activeState.awb, frameContext.awb, awbStats,\n+\t\t\t frameContext.lux.lux, metadata);\n }\n \n-RGB<double> Awb::calculateRgbMeans(const IPAFrameContext &frameContext, const rkisp1_cif_isp_awb_stat *awb) const\n-{\n-\tVector<double, 3> rgbMeans;\n-\n-\tif (rgbMode_) {\n-\t\trgbMeans = {{\n-\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_cr_or_r),\n-\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_y_or_g),\n-\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_cb_or_b)\n-\t\t}};\n-\t} else {\n-\t\t/* Get the YCbCr mean values */\n-\t\tVector<double, 3> yuvMeans({\n-\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_y_or_g),\n-\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_cb_or_b),\n-\t\t\tstatic_cast<double>(awb->awb_mean[0].mean_cr_or_r)\n-\t\t});\n-\n-\t\t/*\n-\t\t * Convert from YCbCr to RGB. The hardware uses the following\n-\t\t * formulas:\n-\t\t *\n-\t\t * Y  =  16 + 0.2500 R + 0.5000 G + 0.1094 B\n-\t\t * Cb = 128 - 0.1406 R - 0.2969 G + 0.4375 B\n-\t\t * Cr = 128 + 0.4375 R - 0.3750 G - 0.0625 B\n-\t\t *\n-\t\t * This seems to be based on limited range BT.601 with Q1.6\n-\t\t * precision.\n-\t\t *\n-\t\t * The inverse matrix is:\n-\t\t *\n-\t\t * [[1,1636, -0,0623,  1,6008]\n-\t\t *  [1,1636, -0,4045, -0,7949]\n-\t\t *  [1,1636,  1,9912, -0,0250]]\n-\t\t */\n-\t\tstatic const Matrix<double, 3, 3> yuv2rgbMatrix({\n-\t\t\t1.1636, -0.0623,  1.6008,\n-\t\t\t1.1636, -0.4045, -0.7949,\n-\t\t\t1.1636,  1.9912, -0.0250\n-\t\t});\n-\t\tstatic const Vector<double, 3> yuv2rgbOffset({\n-\t\t\t16, 128, 128\n-\t\t});\n-\n-\t\trgbMeans = yuv2rgbMatrix * (yuvMeans - yuv2rgbOffset);\n-\n-\t\t/*\n-\t\t * Due to hardware rounding errors in the YCbCr means, the\n-\t\t * calculated RGB means may be negative. This would lead to\n-\t\t * negative gains, messing up calculation. Prevent this by\n-\t\t * clamping the means to positive values.\n-\t\t */\n-\t\trgbMeans = rgbMeans.max(0.0);\n-\t}\n-\n-\t/*\n-\t * The ISP computes the AWB means after applying the CCM. Apply the\n-\t * inverse as we want to get the raw means before the colour gains.\n-\t */\n-\trgbMeans = frameContext.ccm.ccm.inverse() * rgbMeans;\n-\n-\t/*\n-\t * The ISP computes the AWB means after applying the colour gains,\n-\t * divide by the gains that were used to get the raw means from the\n-\t * sensor. Apply a minimum value to avoid divisions by near-zero.\n-\t */\n-\trgbMeans /= frameContext.awb.gains.max(0.01);\n-\n-\treturn rgbMeans;\n-}\n \n REGISTER_IPA_ALGORITHM(Awb, \"Awb\")\n \ndiff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h\nindex 60d9ef111495..c7cb59a75bde 100644\n--- a/src/ipa/rkisp1/algorithms/awb.h\n+++ b/src/ipa/rkisp1/algorithms/awb.h\n@@ -7,17 +7,25 @@\n \n #pragma once\n \n-#include \"libcamera/internal/vector.h\"\n+#include <linux/rkisp1-config.h>\n+\n+#include <libcamera/controls.h>\n+\n+#include \"libcamera/internal/value_node.h\"\n \n #include \"libipa/awb.h\"\n-#include \"libipa/interpolator.h\"\n+#include \"libipa/fixedpoint.h\"\n \n #include \"algorithm.h\"\n+#include \"ipa_context.h\"\n+#include \"params.h\"\n \n namespace libcamera {\n \n namespace ipa::rkisp1::algorithms {\n \n+class RkISP1AwbStats;\n+\n class Awb : public Algorithm\n {\n public:\n@@ -38,10 +46,10 @@ public:\n \t\t     ControlList &metadata) override;\n \n private:\n-\tRGB<double> calculateRgbMeans(const IPAFrameContext &frameContext,\n-\t\t\t\t      const rkisp1_cif_isp_awb_stat *awb) const;\n+\tRkISP1AwbStats calculateRgbMeans(const IPAFrameContext &frameContext,\n+\t\t\t\t\t const rkisp1_stat_buffer *stats) const;\n \n-\tstd::unique_ptr<AwbAlgorithm> awbAlgo_;\n+\tAwbAlgorithm<UQ<2, 8>> awbAlgo_;\n \n \tbool rgbMode_;\n };\ndiff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h\nindex e5f70d934d67..c36c1f7e0084 100644\n--- a/src/ipa/rkisp1/ipa_context.h\n+++ b/src/ipa/rkisp1/ipa_context.h\n@@ -25,6 +25,7 @@\n #include \"libcamera/internal/vector.h\"\n \n #include \"libipa/agc_mean_luminance.h\"\n+#include \"libipa/awb.h\"\n #include \"libipa/camera_sensor_helper.h\"\n #include \"libipa/fc_queue.h\"\n #include \"libipa/fixedpoint.h\"\n@@ -48,15 +49,17 @@ struct IPAHwSettings {\n \tbool compand;\n };\n \n+struct RKISP1AwbSession {\n+\tstruct rkisp1_cif_isp_window measureWindow;\n+\tbool enabled;\n+};\n+\n struct IPASessionConfiguration {\n \tstruct {\n \t\tstruct rkisp1_cif_isp_window measureWindow;\n \t} agc;\n \n-\tstruct {\n-\t\tstruct rkisp1_cif_isp_window measureWindow;\n-\t\tbool enabled;\n-\t} awb;\n+\tstruct RKISP1AwbSession awb;\n \n \tstruct {\n \t\tbool supported;\n@@ -100,17 +103,7 @@ struct IPAActiveState {\n \t\tutils::Duration maxFrameDuration;\n \t} agc;\n \n-\tstruct {\n-\t\tstruct AwbState {\n-\t\t\tRGB<double> gains;\n-\t\t\tunsigned int temperatureK;\n-\t\t};\n-\n-\t\tAwbState manual;\n-\t\tAwbState automatic;\n-\n-\t\tbool autoEnabled;\n-\t} awb;\n+\tipa::awb::ActiveState awb;\n \n \tstruct {\n \t\tMatrix<float, 3, 3> manual;\n@@ -176,11 +169,7 @@ struct IPAFrameContext : public FrameContext {\n \t\tbool autoGainModeChange;\n \t} agc;\n \n-\tstruct {\n-\t\tRGB<double> gains;\n-\t\tbool autoEnabled;\n-\t\tunsigned int temperatureK;\n-\t} awb;\n+\tipa::awb::FrameContext awb;\n \n \tstruct {\n \t\tfloat actualBrightness;\n",
    "prefixes": [
        "v3",
        "01/35"
    ]
}