{"id":19136,"url":"https://patchwork.libcamera.org/api/patches/19136/?format=json","web_url":"https://patchwork.libcamera.org/patch/19136/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20231013074841.16972-12-naush@raspberrypi.com>","date":"2023-10-13T07:48:32","name":"[libcamera-devel,v2,11/20] ipa: rpi: Add new algorithms for PiSP","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"9ba71e96ce0497b90fb85370878196656081a13c","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/?format=json","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/19136/mbox/","series":[{"id":4049,"url":"https://patchwork.libcamera.org/api/series/4049/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=4049","date":"2023-10-13T07:48:21","name":"Raspberry Pi: Preliminary PiSP support","version":2,"mbox":"https://patchwork.libcamera.org/series/4049/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/19136/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/19136/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 84763C32BE\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 13 Oct 2023 07:49:04 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0FC4C62986;\n\tFri, 13 Oct 2023 09:49:04 +0200 (CEST)","from mail-ej1-x62e.google.com (mail-ej1-x62e.google.com\n\t[IPv6:2a00:1450:4864:20::62e])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7BBE262985\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 13 Oct 2023 09:48:55 +0200 (CEST)","by mail-ej1-x62e.google.com with SMTP id\n\ta640c23a62f3a-9adca291f99so269716266b.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 13 Oct 2023 00:48:55 -0700 (PDT)","from localhost.localdomain ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\tm16-20020a7bca50000000b003fee6e170f9sm1791890wml.45.2023.10.13.00.48.53\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 13 Oct 2023 00:48:53 -0700 (PDT)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1697183344;\n\tbh=InJk5nE/LHRBOTGRWjRFoKav2SdmaIyuGdMs8XQP2Ek=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=AuFTsBtJf7UG715DjW7aBOCQ+bqPYsNooJrskZYnPjbp90VbKbvfjVvPEdb6xf+ic\n\tl6g2QwNxgD8ynnlsLJV9Id86NvOX9KHMBc/UttfCdotWN8PL/0RrCH3yRwOQADYmOP\n\tarmPGtS5BR42Xfv1BV1s4Y8Y9/T/OxSM2jwSjw9uWcSkv32SeR0pPViVTBMGZdeGqX\n\txq4Aapjx8QS4dKZ9uWRZPOSDCw5pzxdF/POVaCP3fECY1BkLq1RNpvb+Q+D1u0tTHG\n\tbyU6BOG9IX3sC6Hlt5lW30CojRZoqIPWUY59YggI0gigNadA5JpXjcI7DEFGuAyNVY\n\tDS+3TbCeh6tzA==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1697183335; x=1697788135;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=VR+P5mQIrApbu0Xnw/5rpn4ma5izTxpcrx2OEmGcuNM=;\n\tb=P0GdK46u89r+6WZ19KATX+/h8tCl/e241FQ2t4wvmSS9Lw4fDMI9xWGngpsbTGwegx\n\tBQI/ghglm14/4Oj11OhfdTdNRp7kQ5EdnAoj2oB0vjIAtZKCoHZ+XBFHX32Ga4i1RGjZ\n\tJIPB0CYwRSIs6WHfB+tm3wQqsSKFt+aNV7F6bkWktS3ATW5SUT+iUcJZYXDEe0cnZLYE\n\tNwIv2yRq0BBBYRs+4aDapqyBoSbg2avrN8uNxlt8g2oLW+H6ax8Pb7Bl+DJT9+LL4HkT\n\tcynNuLej2A7Wia3MC13D7nNaBqYdGsiNJBl9hkXzJzPrFPZz/PRqc6Q3THRE3Bpobvyk\n\tBc/Q=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"P0GdK46u\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1697183335; x=1697788135;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=VR+P5mQIrApbu0Xnw/5rpn4ma5izTxpcrx2OEmGcuNM=;\n\tb=DzdqUT5pzxNRE1l1pSqI0IFk8oRpsfGckAy5k6Va42c3TPCLHzFZYULpZ52HQx/+/K\n\tb1+zfnPt/D08g2fT3S1kNK3RdRM0eM5xfCQ4v/PmG1k0K4p3i9zK55zc8RW279n2HJNB\n\tdgqWEFeC8L8rLmnXI3lfeFrukHeZ3LXBHpTG3uKJ2BRXl2tg3xfYkFnqp7S88b4QWOWL\n\tu3ESYLgzf1JvcWu+ZIFAjEqkeHg2Y5xto0qhtMJu5CAJA4Nf+MHSdJrbTLso8YGFtPRs\n\tuVJrIJsaPRvnWDKpTEzgQMqWWzGccf7uuRZT5KilLI/Jtj3ObOiEeai6yAHmaXTx4ZTq\n\tenJQ==","X-Gm-Message-State":"AOJu0YzF2oxM0p4WLnvVJLqZWwE2lNxKp3rz3qm5Z1QkRaZbEAcYx9Sj\n\tlM69JpmKpS117uvNWV4e19BCxhXta0cSIi1mhwUUSg==","X-Google-Smtp-Source":"AGHT+IHFq8vmCifoWhvGboCptFa00ahf602vhXu3HRe1bXrfJGbvv/5WCyYbdTFD3TQryFkjgpVUoA==","X-Received":"by 2002:a17:906:1d:b0:9b2:9a0e:9972 with SMTP id\n\t29-20020a170906001d00b009b29a0e9972mr24309921eja.13.1697183334359; \n\tFri, 13 Oct 2023 00:48:54 -0700 (PDT)","To":"libcamera-devel@lists.libcamera.org","Date":"Fri, 13 Oct 2023 08:48:32 +0100","Message-Id":"<20231013074841.16972-12-naush@raspberrypi.com>","X-Mailer":"git-send-email 2.34.1","In-Reply-To":"<20231013074841.16972-1-naush@raspberrypi.com>","References":"<20231013074841.16972-1-naush@raspberrypi.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v2 11/20] ipa: rpi: Add new algorithms for\n\tPiSP","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>","From":"Naushir Patuck via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Naushir Patuck <naush@raspberrypi.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Add new CAC, HDR, Saturation and Tonemapping algorithms.\n\nAdd a new Denoise algorithm that handles spatial/temporal/colour denoise\nthrough one interface. With this change, the old SDN algorithm is now\nconsidered deprecated and a warning message will be displayed if it is\nenabled.\n\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\nReviewed-by: David Plowman <david.plowman@raspberrypi.com>\n---\n src/ipa/rpi/controller/agc_status.h        |   3 +\n src/ipa/rpi/controller/cac_status.h        |  16 ++\n src/ipa/rpi/controller/denoise_status.h    |  19 ++\n src/ipa/rpi/controller/hdr_algorithm.h     |  25 ++\n src/ipa/rpi/controller/hdr_status.h        |  19 ++\n src/ipa/rpi/controller/meson.build         |   5 +\n src/ipa/rpi/controller/rpi/cac.cpp         |  81 +++++++\n src/ipa/rpi/controller/rpi/cac.h           |  38 +++\n src/ipa/rpi/controller/rpi/denoise.cpp     | 156 ++++++++++++\n src/ipa/rpi/controller/rpi/denoise.h       |  49 ++++\n src/ipa/rpi/controller/rpi/hdr.cpp         | 270 +++++++++++++++++++++\n src/ipa/rpi/controller/rpi/hdr.h           |  72 ++++++\n src/ipa/rpi/controller/rpi/saturation.cpp  |  57 +++++\n src/ipa/rpi/controller/rpi/saturation.h    |  32 +++\n src/ipa/rpi/controller/rpi/sdn.cpp         |   2 +\n src/ipa/rpi/controller/rpi/tonemap.cpp     |  61 +++++\n src/ipa/rpi/controller/rpi/tonemap.h       |  35 +++\n src/ipa/rpi/controller/saturation_status.h |  13 +\n src/ipa/rpi/controller/stitch_status.h     |  17 ++\n src/ipa/rpi/controller/tonemap_status.h    |  17 ++\n 20 files changed, 987 insertions(+)\n create mode 100644 src/ipa/rpi/controller/cac_status.h\n create mode 100644 src/ipa/rpi/controller/hdr_algorithm.h\n create mode 100644 src/ipa/rpi/controller/hdr_status.h\n create mode 100644 src/ipa/rpi/controller/rpi/cac.cpp\n create mode 100644 src/ipa/rpi/controller/rpi/cac.h\n create mode 100644 src/ipa/rpi/controller/rpi/denoise.cpp\n create mode 100644 src/ipa/rpi/controller/rpi/denoise.h\n create mode 100644 src/ipa/rpi/controller/rpi/hdr.cpp\n create mode 100644 src/ipa/rpi/controller/rpi/hdr.h\n create mode 100644 src/ipa/rpi/controller/rpi/saturation.cpp\n create mode 100644 src/ipa/rpi/controller/rpi/saturation.h\n create mode 100644 src/ipa/rpi/controller/rpi/tonemap.cpp\n create mode 100644 src/ipa/rpi/controller/rpi/tonemap.h\n create mode 100644 src/ipa/rpi/controller/saturation_status.h\n create mode 100644 src/ipa/rpi/controller/stitch_status.h\n create mode 100644 src/ipa/rpi/controller/tonemap_status.h","diff":"diff --git a/src/ipa/rpi/controller/agc_status.h b/src/ipa/rpi/controller/agc_status.h\nindex e5c4ee2239d9..68f899585740 100644\n--- a/src/ipa/rpi/controller/agc_status.h\n+++ b/src/ipa/rpi/controller/agc_status.h\n@@ -10,6 +10,8 @@\n \n #include <libcamera/base/utils.h>\n \n+#include \"hdr_status.h\"\n+\n /*\n  * The AGC algorithm process method should post an AgcStatus into the image\n  * metadata under the tag \"agc.status\".\n@@ -37,6 +39,7 @@ struct AgcStatus {\n \tlibcamera::utils::Duration fixedShutter;\n \tdouble fixedAnalogueGain;\n \tunsigned int channel;\n+\tHdrStatus hdr;\n };\n \n struct AgcPrepareStatus {\ndiff --git a/src/ipa/rpi/controller/cac_status.h b/src/ipa/rpi/controller/cac_status.h\nnew file mode 100644\nindex 000000000000..475d4c5cc734\n--- /dev/null\n+++ b/src/ipa/rpi/controller/cac_status.h\n@@ -0,0 +1,16 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2023 Raspberry Pi Ltd\n+ *\n+ * CAC (Chromatic Abberation Correction) algorithm status\n+ */\n+#pragma once\n+\n+#include \"pwl.h\"\n+\n+struct CacStatus {\n+\tstd::vector<double> lutRx;\n+\tstd::vector<double> lutRy;\n+\tstd::vector<double> lutBx;\n+\tstd::vector<double> lutBy;\n+};\ndiff --git a/src/ipa/rpi/controller/denoise_status.h b/src/ipa/rpi/controller/denoise_status.h\nindex f6b9ee29dad6..4d2bd291f2f1 100644\n--- a/src/ipa/rpi/controller/denoise_status.h\n+++ b/src/ipa/rpi/controller/denoise_status.h\n@@ -14,3 +14,22 @@ struct DenoiseStatus {\n \tdouble strength;\n \tunsigned int mode;\n };\n+\n+struct SdnStatus {\n+\tdouble noiseConstant;\n+\tdouble noiseSlope;\n+\tdouble noiseConstant2;\n+\tdouble noiseSlope2;\n+\tdouble strength;\n+};\n+\n+struct CdnStatus {\n+\tdouble strength;\n+\tdouble threshold;\n+};\n+\n+struct TdnStatus {\n+\tdouble noiseConstant;\n+\tdouble noiseSlope;\n+\tdouble threshold;\n+};\ndiff --git a/src/ipa/rpi/controller/hdr_algorithm.h b/src/ipa/rpi/controller/hdr_algorithm.h\nnew file mode 100644\nindex 000000000000..f622e099b6f5\n--- /dev/null\n+++ b/src/ipa/rpi/controller/hdr_algorithm.h\n@@ -0,0 +1,25 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2023, Raspberry Pi Ltd\n+ *\n+ * hdr_algorithm.h - HDR control algorithm interface\n+ */\n+#pragma once\n+\n+#include <vector>\n+\n+#include \"algorithm.h\"\n+\n+namespace RPiController {\n+\n+class HdrAlgorithm : public Algorithm\n+{\n+public:\n+\tHdrAlgorithm(Controller *controller)\n+\t\t: Algorithm(controller) {}\n+\t/* An HDR algorithm must provide the following: */\n+\tvirtual int setMode(std::string const &modeName) = 0;\n+\tvirtual std::vector<unsigned int> getChannels() const = 0;\n+};\n+\n+} /* namespace RPiController */\ndiff --git a/src/ipa/rpi/controller/hdr_status.h b/src/ipa/rpi/controller/hdr_status.h\nnew file mode 100644\nindex 000000000000..24b1a9358871\n--- /dev/null\n+++ b/src/ipa/rpi/controller/hdr_status.h\n@@ -0,0 +1,19 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2023 Raspberry Pi Ltd\n+ *\n+ * hdr_status.h - HDR control algorithm status\n+ */\n+#pragma once\n+\n+#include <string>\n+\n+/*\n+ * The HDR algorithm process method should post an HdrStatus into the image\n+ * metadata under the tag \"hdr.status\".\n+ */\n+\n+struct HdrStatus {\n+\tstd::string mode;\n+\tstd::string channel;\n+};\ndiff --git a/src/ipa/rpi/controller/meson.build b/src/ipa/rpi/controller/meson.build\nindex 20b9cda93661..32a4d31cfada 100644\n--- a/src/ipa/rpi/controller/meson.build\n+++ b/src/ipa/rpi/controller/meson.build\n@@ -12,14 +12,19 @@ rpi_ipa_controller_sources = files([\n     'rpi/alsc.cpp',\n     'rpi/awb.cpp',\n     'rpi/black_level.cpp',\n+    'rpi/cac.cpp',\n     'rpi/ccm.cpp',\n     'rpi/contrast.cpp',\n+    'rpi/denoise.cpp',\n     'rpi/dpc.cpp',\n     'rpi/geq.cpp',\n+    'rpi/hdr.cpp',\n     'rpi/lux.cpp',\n     'rpi/noise.cpp',\n+    'rpi/saturation.cpp',\n     'rpi/sdn.cpp',\n     'rpi/sharpen.cpp',\n+    'rpi/tonemap.cpp',\n ])\n \n rpi_ipa_controller_deps = [\ndiff --git a/src/ipa/rpi/controller/rpi/cac.cpp b/src/ipa/rpi/controller/rpi/cac.cpp\nnew file mode 100644\nindex 000000000000..7c123da1530a\n--- /dev/null\n+++ b/src/ipa/rpi/controller/rpi/cac.cpp\n@@ -0,0 +1,81 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2023 Raspberry Pi Ltd\n+ *\n+ * cac.cpp - Chromatic Aberration Correction algorithm\n+ */\n+#include \"cac.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include \"cac_status.h\"\n+\n+using namespace RPiController;\n+using namespace libcamera;\n+\n+LOG_DEFINE_CATEGORY(RPiCac)\n+\n+#define NAME \"rpi.cac\"\n+\n+Cac::Cac(Controller *controller)\n+\t: Algorithm(controller)\n+{\n+}\n+\n+char const *Cac::name() const\n+{\n+\treturn NAME;\n+}\n+\n+int Cac::read(const libcamera::YamlObject &params)\n+{\n+\tarrayToSet(params[\"lut_rx\"], config_.lutRx);\n+\tarrayToSet(params[\"lut_ry\"], config_.lutRy);\n+\tarrayToSet(params[\"lut_bx\"], config_.lutBx);\n+\tarrayToSet(params[\"lut_by\"], config_.lutBy);\n+\tcacStatus_.lutRx = config_.lutRx;\n+\tcacStatus_.lutRy = config_.lutRy;\n+\tcacStatus_.lutBx = config_.lutBx;\n+\tcacStatus_.lutBy = config_.lutBy;\n+\tdouble strength = params[\"strength\"].get<double>(1);\n+\tsetStrength(config_.lutRx, cacStatus_.lutRx, strength);\n+\tsetStrength(config_.lutBx, cacStatus_.lutBx, strength);\n+\tsetStrength(config_.lutRy, cacStatus_.lutRy, strength);\n+\tsetStrength(config_.lutBy, cacStatus_.lutBy, strength);\n+\treturn 0;\n+}\n+\n+void Cac::initialise()\n+{\n+}\n+\n+void Cac::arrayToSet(const libcamera::YamlObject &params, std::vector<double> &inputArray)\n+{\n+\tint num = 0;\n+\tconst Size &size = getHardwareConfig().cacRegions;\n+\tinputArray.resize((size.width + 1) * (size.height + 1));\n+\tfor (const auto &p : params.asList()) {\n+\t\tinputArray[num++] = p.get<double>(0);\n+\t}\n+}\n+\n+void Cac::setStrength(std::vector<double> &inputArray, std::vector<double> &outputArray,\n+\t\t      double strengthFactor)\n+{\n+\tint num = 0;\n+\tfor (const auto &p : inputArray) {\n+\t\toutputArray[num++] = p * strengthFactor;\n+\t}\n+}\n+\n+void Cac::prepare(Metadata *imageMetadata)\n+{\n+\timageMetadata->set(\"cac.status\", cacStatus_);\n+}\n+\n+// Register algorithm with the system.\n+static Algorithm *Create(Controller *controller)\n+{\n+\treturn (Algorithm *)new Cac(controller);\n+}\n+static RegisterAlgorithm reg(NAME, &Create);\ndiff --git a/src/ipa/rpi/controller/rpi/cac.h b/src/ipa/rpi/controller/rpi/cac.h\nnew file mode 100644\nindex 000000000000..419180ab7d29\n--- /dev/null\n+++ b/src/ipa/rpi/controller/rpi/cac.h\n@@ -0,0 +1,38 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2023, Raspberry Pi Ltd\n+ *\n+ * cac.hpp - CAC control algorithm\n+ */\n+#pragma once\n+\n+#include \"algorithm.h\"\n+#include \"cac_status.h\"\n+\n+namespace RPiController {\n+\n+struct CacConfig {\n+\tstd::vector<double> lutRx;\n+\tstd::vector<double> lutRy;\n+\tstd::vector<double> lutBx;\n+\tstd::vector<double> lutBy;\n+};\n+\n+class Cac : public Algorithm\n+{\n+public:\n+\tCac(Controller *controller = NULL);\n+\tchar const *name() const override;\n+\tint read(const libcamera::YamlObject &params) override;\n+\tvoid initialise() override;\n+\tvoid prepare(Metadata *imageMetadata) override;\n+\tvoid setStrength(std::vector<double> &inputArray, std::vector<double> &outputArray,\n+\t\t\t double strengthFactor);\n+\n+private:\n+\tCacConfig config_;\n+\tCacStatus cacStatus_;\n+\tvoid arrayToSet(const libcamera::YamlObject &params, std::vector<double> &inputArray);\n+};\n+\n+} // namespace RPiController\ndiff --git a/src/ipa/rpi/controller/rpi/denoise.cpp b/src/ipa/rpi/controller/rpi/denoise.cpp\nnew file mode 100644\nindex 000000000000..440ee4425534\n--- /dev/null\n+++ b/src/ipa/rpi/controller/rpi/denoise.cpp\n@@ -0,0 +1,156 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2022 Raspberry Pi Ltd\n+ *\n+ * Denoise.cpp - Denoise (spatial, colour, temporal) control algorithm\n+ */\n+#include \"denoise.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include \"denoise_status.h\"\n+#include \"noise_status.h\"\n+\n+using namespace RPiController;\n+using namespace libcamera;\n+\n+LOG_DEFINE_CATEGORY(RPiDenoise)\n+\n+// Calculate settings for the denoise blocks using the noise profile in\n+// the image metadata.\n+\n+#define NAME \"rpi.denoise\"\n+\n+Denoise::Denoise(Controller *controller)\n+\t: DenoiseAlgorithm(controller), mode_(DenoiseMode::ColourHighQuality)\n+{\n+}\n+\n+char const *Denoise::name() const\n+{\n+\treturn NAME;\n+}\n+\n+int Denoise::read(const libcamera::YamlObject &params)\n+{\n+\tsdnEnable_ = params.contains(\"sdn\");\n+\tif (sdnEnable_) {\n+\t\tauto &sdnParams = params[\"sdn\"];\n+\t\tsdnDeviation_ = sdnParams[\"deviation\"].get<double>(3.2);\n+\t\tsdnStrength_ = sdnParams[\"strength\"].get<double>(0.25);\n+\t\tsdnDeviation2_ = sdnParams[\"deviation2\"].get<double>(sdnDeviation_);\n+\t\tsdnDeviationNoTdn_ = sdnParams[\"deviation_no_tdn\"].get<double>(sdnDeviation_);\n+\t\tsdnStrengthNoTdn_ = sdnParams[\"strength_no_tdn\"].get<double>(sdnStrength_);\n+\t\tsdnTdnBackoff_ = sdnParams[\"backoff\"].get<double>(0.75);\n+\t}\n+\n+\tcdnEnable_ = params.contains(\"cdn\");\n+\tif (cdnEnable_) {\n+\t\tauto &cdnParams = params[\"cdn\"];\n+\t\tcdnDeviation_ = cdnParams[\"deviation\"].get<double>(120);\n+\t\tcdnStrength_ = cdnParams[\"strength\"].get<double>(0.2);\n+\t}\n+\n+\ttdnEnable_ = params.contains(\"tdn\");\n+\tif (tdnEnable_) {\n+\t\tauto &tdnParams = params[\"tdn\"];\n+\t\ttdnDeviation_ = tdnParams[\"deviation\"].get<double>(0.5);\n+\t\ttdnThreshold_ = tdnParams[\"threshold\"].get<double>(0.75);\n+\t} else if (sdnEnable_) {\n+\t\t/*\n+\t\t * If SDN is enabled but TDN isn't, overwrite all the SDN settings\n+\t\t * with the \"no TDN\" versions. This makes it easier to enable or\n+\t\t * disable TDN in the tuning file without editing all the other\n+\t\t * parameters.\n+\t\t */\n+\t\tsdnDeviation_ = sdnDeviation2_ = sdnDeviationNoTdn_;\n+\t\tsdnStrength_ = sdnStrengthNoTdn_;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void Denoise::initialise()\n+{\n+}\n+\n+void Denoise::switchMode([[maybe_unused]] CameraMode const &cameraMode,\n+\t\t\t [[maybe_unused]] Metadata *metadata)\n+{\n+\t/* A mode switch effectively resets temporal denoise and it has to start over. */\n+\tcurrentSdnDeviation_ = sdnDeviationNoTdn_;\n+\tcurrentSdnStrength_ = sdnStrengthNoTdn_;\n+\tcurrentSdnDeviation2_ = sdnDeviationNoTdn_;\n+}\n+\n+void Denoise::prepare(Metadata *imageMetadata)\n+{\n+\tstruct NoiseStatus noiseStatus = {};\n+\tnoiseStatus.noiseSlope = 3.0; // in case no metadata\n+\tif (imageMetadata->get(\"noise.status\", noiseStatus) != 0)\n+\t\tLOG(RPiDenoise, Warning) << \"no noise profile found\";\n+\n+\tLOG(RPiDenoise, Debug)\n+\t\t<< \"Noise profile: constant \" << noiseStatus.noiseConstant\n+\t\t<< \" slope \" << noiseStatus.noiseSlope;\n+\n+\tif (mode_ == DenoiseMode::Off)\n+\t\treturn;\n+\n+\tif (sdnEnable_) {\n+\t\tstruct SdnStatus sdn;\n+\t\tsdn.noiseConstant = noiseStatus.noiseConstant * currentSdnDeviation_;\n+\t\tsdn.noiseSlope = noiseStatus.noiseSlope * currentSdnDeviation_;\n+\t\tsdn.noiseConstant2 = noiseStatus.noiseConstant * sdnDeviation2_;\n+\t\tsdn.noiseSlope2 = noiseStatus.noiseSlope * currentSdnDeviation2_;\n+\t\tsdn.strength = currentSdnStrength_;\n+\t\timageMetadata->set(\"sdn.status\", sdn);\n+\t\tLOG(RPiDenoise, Debug)\n+\t\t\t<< \"const \" << sdn.noiseConstant\n+\t\t\t<< \" slope \" << sdn.noiseSlope\n+\t\t\t<< \" str \" << sdn.strength\n+\t\t\t<< \" const2 \" << sdn.noiseConstant2\n+\t\t\t<< \" slope2 \" << sdn.noiseSlope2;\n+\n+\t\t/* For the next frame, we back off the SDN parameters as TDN ramps up. */\n+\t\tdouble f = sdnTdnBackoff_;\n+\t\tcurrentSdnDeviation_ = f * currentSdnDeviation_ + (1 - f) * sdnDeviation_;\n+\t\tcurrentSdnStrength_ = f * currentSdnStrength_ + (1 - f) * sdnStrength_;\n+\t\tcurrentSdnDeviation2_ = f * currentSdnDeviation2_ + (1 - f) * sdnDeviation2_;\n+\t}\n+\n+\tif (tdnEnable_) {\n+\t\tstruct TdnStatus tdn;\n+\t\ttdn.noiseConstant = noiseStatus.noiseConstant * tdnDeviation_;\n+\t\ttdn.noiseSlope = noiseStatus.noiseSlope * tdnDeviation_;\n+\t\ttdn.threshold = tdnThreshold_;\n+\t\timageMetadata->set(\"tdn.status\", tdn);\n+\t\tLOG(RPiDenoise, Debug)\n+\t\t\t<< \"programmed tdn threshold \" << tdn.threshold\n+\t\t\t<< \" constant \" << tdn.noiseConstant\n+\t\t\t<< \" slope \" << tdn.noiseSlope;\n+\t}\n+\n+\tif (cdnEnable_ && mode_ != DenoiseMode::ColourOff) {\n+\t\tstruct CdnStatus cdn;\n+\t\tcdn.threshold = cdnDeviation_ * noiseStatus.noiseSlope + noiseStatus.noiseConstant;\n+\t\tcdn.strength = cdnStrength_;\n+\t\timageMetadata->set(\"cdn.status\", cdn);\n+\t\tLOG(RPiDenoise, Debug)\n+\t\t\t<< \"programmed cdn threshold \" << cdn.threshold\n+\t\t\t<< \" strength \" << cdn.strength;\n+\t}\n+}\n+\n+void Denoise::setMode(DenoiseMode mode)\n+{\n+\t// We only distinguish between off and all other modes.\n+\tmode_ = mode;\n+}\n+\n+// Register algorithm with the system.\n+static Algorithm *Create(Controller *controller)\n+{\n+\treturn (Algorithm *)new Denoise(controller);\n+}\n+static RegisterAlgorithm reg(NAME, &Create);\ndiff --git a/src/ipa/rpi/controller/rpi/denoise.h b/src/ipa/rpi/controller/rpi/denoise.h\nnew file mode 100644\nindex 000000000000..88b37663e569\n--- /dev/null\n+++ b/src/ipa/rpi/controller/rpi/denoise.h\n@@ -0,0 +1,49 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2022, Raspberry Pi Ltd\n+ *\n+ * denoise.hpp - Denoise (spatial, colour, temporal) control algorithm\n+ */\n+#pragma once\n+\n+#include \"algorithm.h\"\n+#include \"denoise_algorithm.h\"\n+\n+namespace RPiController {\n+\n+// Algorithm to calculate correct denoise settings.\n+\n+class Denoise : public DenoiseAlgorithm\n+{\n+public:\n+\tDenoise(Controller *controller);\n+\tchar const *name() const override;\n+\tint read(const libcamera::YamlObject &params) override;\n+\tvoid initialise() override;\n+\tvoid switchMode(CameraMode const &cameraMode, Metadata *metadata) override;\n+\tvoid prepare(Metadata *imageMetadata) override;\n+\tvoid setMode(DenoiseMode mode) override;\n+\n+private:\n+\tdouble sdnDeviation_;\n+\tdouble sdnStrength_;\n+\tdouble sdnDeviation2_;\n+\tdouble sdnDeviationNoTdn_;\n+\tdouble sdnStrengthNoTdn_;\n+\tdouble sdnTdnBackoff_;\n+\tdouble cdnDeviation_;\n+\tdouble cdnStrength_;\n+\tdouble tdnDeviation_;\n+\tdouble tdnThreshold_;\n+\tDenoiseMode mode_;\n+\tbool tdnEnable_;\n+\tbool sdnEnable_;\n+\tbool cdnEnable_;\n+\n+\t/* SDN parameters attenuate over time if TDN is running. */\n+\tdouble currentSdnDeviation_;\n+\tdouble currentSdnStrength_;\n+\tdouble currentSdnDeviation2_;\n+};\n+\n+} // namespace RPiController\ndiff --git a/src/ipa/rpi/controller/rpi/hdr.cpp b/src/ipa/rpi/controller/rpi/hdr.cpp\nnew file mode 100644\nindex 000000000000..295e4c5f1c0a\n--- /dev/null\n+++ b/src/ipa/rpi/controller/rpi/hdr.cpp\n@@ -0,0 +1,270 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2023 Raspberry Pi Ltd\n+ *\n+ * hdr.cpp - HDR control algorithm\n+ */\n+\n+#include \"hdr.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include \"../agc_status.h\"\n+#include \"../stitch_status.h\"\n+#include \"../tonemap_status.h\"\n+\n+using namespace RPiController;\n+using namespace libcamera;\n+\n+LOG_DEFINE_CATEGORY(RPiHdr)\n+\n+#define NAME \"rpi.hdr\"\n+\n+void HdrConfig::read(const libcamera::YamlObject &params, const std::string &modeName)\n+{\n+\tname = modeName;\n+\n+\tif (!params.contains(\"cadence\"))\n+\t\tLOG(RPiHdr, Fatal) << \"No cadence for HDR mode \" << name;\n+\tcadence = params[\"cadence\"].getList<unsigned int>().value();\n+\tif (cadence.empty())\n+\t\tLOG(RPiHdr, Fatal) << \"Empty cadence in HDR mode \" << name;\n+\n+\t/*\n+\t * In the JSON file it's easier to use the channel name as the key, but\n+\t * for us it's convenient to swap them over.\n+\t */\n+\tfor (const auto &[k, v] : params[\"channel_map\"].asDict())\n+\t\tchannelMap[v.get<unsigned int>().value()] = k;\n+\n+\t/* Read any tonemap parameters. */\n+\ttonemapEnable = params[\"tonemap_enable\"].get<int>(0);\n+\tdetailConstant = params[\"detail_constant\"].get<uint16_t>(50);\n+\tdetailSlope = params[\"detail_slope\"].get<double>(8.0);\n+\tiirStrength = params[\"iir_strength\"].get<double>(8.0);\n+\tstrength = params[\"strength\"].get<double>(1.5);\n+\n+\tif (tonemapEnable) {\n+\t\t/* We need either an explicit tonemap, or the information to build them dynamically. */\n+\t\tif (params.contains(\"tonemap\")) {\n+\t\t\tif (tonemap.read(params[\"tonemap\"]))\n+\t\t\t\tLOG(RPiHdr, Fatal) << \"Failed to read tonemap in HDR mode \" << name;\n+\t\t} else {\n+\t\t\tif (target.read(params[\"target\"]))\n+\t\t\t\tLOG(RPiHdr, Fatal) << \"Failed to read target in HDR mode \" << name;\n+\t\t\tif (maxSlope.read(params[\"max_slope\"]))\n+\t\t\t\tLOG(RPiHdr, Fatal) << \"Failed to read max_slope in HDR mode \" << name;\n+\t\t\tminSlope = params[\"min_slope\"].get<double>(1.0);\n+\t\t\tmaxGain = params[\"max_gain\"].get<double>(64.0);\n+\t\t\tstep = params[\"step\"].get<double>(0.05);\n+\t\t\tspeed = params[\"speed\"].get<double>(0.5);\n+\t\t}\n+\t}\n+\n+\t/* Read any stitch parameters. */\n+\tstitchEnable = params[\"stitch_enable\"].get<int>(0);\n+\tthresholdLo = params[\"threshold_lo\"].get<uint16_t>(50000);\n+\tmotionThreshold = params[\"motion_threshold\"].get<double>(0.005);\n+\tdiffPower = params[\"diff_power\"].get<uint8_t>(13);\n+\tif (diffPower > 15)\n+\t\tLOG(RPiHdr, Fatal) << \"Bad diff_power value in HDR mode \" << name;\n+}\n+\n+Hdr::Hdr(Controller *controller)\n+\t: HdrAlgorithm(controller)\n+{\n+}\n+\n+char const *Hdr::name() const\n+{\n+\treturn NAME;\n+}\n+\n+int Hdr::read(const libcamera::YamlObject &params)\n+{\n+\t/* Make an \"HDR off\" mode by default so that tuning files don't have to. */\n+\tHdrConfig &offMode = config_[\"Off\"];\n+\toffMode.name = \"Off\";\n+\toffMode.cadence = { 0 };\n+\toffMode.channelMap[0] = \"None\";\n+\tstatus_.mode = offMode.name;\n+\tdelayedStatus_.mode = offMode.name;\n+\n+\t/*\n+\t * But we still allow the tuning file to override the \"Off\" mode if it wants.\n+\t * For example, maybe an application will make channel 0 be the \"short\"\n+\t * channel, in order to apply other AGC controls to it.\n+\t */\n+\tfor (const auto &[key, value] : params.asDict())\n+\t\tconfig_[key].read(value, key);\n+\n+\treturn 0;\n+}\n+\n+int Hdr::setMode(std::string const &mode)\n+{\n+\t/* Always validate the mode, so it can be used later without checking. */\n+\tauto it = config_.find(mode);\n+\tif (it == config_.end()) {\n+\t\tLOG(RPiHdr, Warning) << \"No such HDR mode \" << mode;\n+\t\treturn -1;\n+\t}\n+\n+\tstatus_.mode = it->second.name;\n+\n+\treturn 0;\n+}\n+\n+std::vector<unsigned int> Hdr::getChannels() const\n+{\n+\treturn config_.at(status_.mode).cadence;\n+}\n+\n+void Hdr::updateAgcStatus(Metadata *metadata)\n+{\n+\tstd::scoped_lock lock(*metadata);\n+\tAgcStatus *agcStatus = metadata->getLocked<AgcStatus>(\"agc.status\");\n+\tif (agcStatus) {\n+\t\tHdrConfig &hdrConfig = config_[status_.mode];\n+\t\tauto it = hdrConfig.channelMap.find(agcStatus->channel);\n+\t\tif (it != hdrConfig.channelMap.end()) {\n+\t\t\tstatus_.channel = it->second;\n+\t\t\tagcStatus->hdr = status_;\n+\t\t} else\n+\t\t\tLOG(RPiHdr, Warning) << \"Channel \" << agcStatus->channel\n+\t\t\t\t\t     << \" not found in mode \" << status_.mode;\n+\t} else\n+\t\tLOG(RPiHdr, Warning) << \"No agc.status found\";\n+}\n+\n+void Hdr::switchMode([[maybe_unused]] CameraMode const &cameraMode, Metadata *metadata)\n+{\n+\tupdateAgcStatus(metadata);\n+\tdelayedStatus_ = status_;\n+}\n+\n+bool Hdr::updateTonemap(StatisticsPtr &stats, HdrConfig &config)\n+{\n+\t/* When there's a change of HDR mode we start over with a new tonemap curve. */\n+\tif (delayedStatus_.mode != previousMode_) {\n+\t\tpreviousMode_ = delayedStatus_.mode;\n+\t\ttonemap_ = Pwl();\n+\t}\n+\n+\t/* No tonemapping. No need to output a tonemap.status. */\n+\tif (!config.tonemapEnable)\n+\t\treturn false;\n+\n+\t/* If an explicit tonemap was given, use it. */\n+\tif (!config.tonemap.empty()) {\n+\t\ttonemap_ = config.tonemap;\n+\t\treturn true;\n+\t}\n+\n+\t/*\n+\t * We only update the tonemap on short frames when in multi-exposure mode. But\n+\t * we still need to output the most recent tonemap. Possibly we should make the\n+\t * config indicate the channels for which we should update the tonemap?\n+\t */\n+\tif (delayedStatus_.mode == \"MultiExposure\" && delayedStatus_.channel != \"short\")\n+\t\treturn true;\n+\n+\t/* Build the tonemap dynamically using the image histogram. */\n+\tPwl tonemap;\n+\ttonemap.append(0, 0);\n+\n+\tdouble prev_input_val = 0;\n+\tdouble prev_output_val = 0;\n+\tconst double step2 = config.step / 2;\n+\tfor (double q = config.step; q < 1.0 - step2; q += config.step) {\n+\t\tdouble q_lo = std::max(0.0, q - step2);\n+\t\tdouble q_hi = std::min(1.0, q + step2);\n+\t\tdouble iqm = stats->yHist.interQuantileMean(q_lo, q_hi);\n+\t\tdouble input_val = std::min(iqm * 64, 65535.0);\n+\n+\t\tif (input_val > prev_input_val + 1) {\n+\t\t\t/* We're going to calcualte a Pwl to map input_val to this output_val. */\n+\t\t\tdouble want_output_val = config.target.eval(q) * 65535;\n+\t\t\t/* But we must ensure we aren't applying too small or too great a local gain. */\n+\t\t\tdouble want_slope = (want_output_val - prev_output_val) / (input_val - prev_input_val);\n+\t\t\tdouble slope = std::clamp(want_slope, config.minSlope,\n+\t\t\t\t\t\t  config.maxSlope.eval(q));\n+\t\t\tdouble output_val = prev_output_val + slope * (input_val - prev_input_val);\n+\t\t\toutput_val = std::min(output_val, config.maxGain * input_val);\n+\t\t\toutput_val = std::clamp(output_val, 0.0, 65535.0);\n+\t\t\t/* Let the tonemap adapte slightly more gently from frame to frame. */\n+\t\t\tif (!tonemap_.empty()) {\n+\t\t\t\tdouble old_output_val = tonemap_.eval(input_val);\n+\t\t\t\toutput_val = config.speed * output_val +\n+\t\t\t\t\t     (1 - config.speed) * old_output_val;\n+\t\t\t}\n+\t\t\tLOG(RPiHdr, Debug) << \"q \" << q << \" input \" << input_val\n+\t\t\t\t\t   << \" output \" << want_output_val << \" slope \" << want_slope\n+\t\t\t\t\t   << \" slope \" << slope << \" output \" << output_val;\n+\t\t\ttonemap.append(input_val, output_val);\n+\t\t\tprev_input_val = input_val;\n+\t\t\tprev_output_val = output_val;\n+\t\t}\n+\t}\n+\n+\ttonemap.append(65535, 65535);\n+\t/* tonemap.debug(); */\n+\ttonemap_ = tonemap;\n+\n+\treturn true;\n+}\n+\n+void Hdr::process(StatisticsPtr &stats, Metadata *imageMetadata)\n+{\n+\t/* Note what HDR channel this frame will be once it comes back to us. */\n+\tupdateAgcStatus(imageMetadata);\n+\n+\t/*\n+\t * Now figure out what HDR channel this frame is. It should be available in the\n+\t * agc.delayed_status, unless this is an early frame after a mode switch, in which\n+\t * case delayedStatus_ should be right.\n+\t */\n+\tAgcStatus agcStatus;\n+\tif (!imageMetadata->get<AgcStatus>(\"agc.delayed_status\", agcStatus))\n+\t\tdelayedStatus_ = agcStatus.hdr;\n+\n+\tauto it = config_.find(delayedStatus_.mode);\n+\tif (it == config_.end()) {\n+\t\t/* Shouldn't be possible. There would be nothing we could do. */\n+\t\tLOG(RPiHdr, Warning) << \"Unexpected HDR mode \" << delayedStatus_.mode;\n+\t\treturn;\n+\t}\n+\n+\tHdrConfig &config = it->second;\n+\n+\tif (updateTonemap(stats, config)) {\n+\t\t/* Add tonemap.status metadata. */\n+\t\tTonemapStatus tonemapStatus;\n+\n+\t\ttonemapStatus.detailConstant = config.detailConstant;\n+\t\ttonemapStatus.detailSlope = config.detailSlope;\n+\t\ttonemapStatus.iirStrength = config.iirStrength;\n+\t\ttonemapStatus.strength = config.strength;\n+\t\ttonemapStatus.tonemap = tonemap_;\n+\n+\t\timageMetadata->set(\"tonemap.status\", tonemapStatus);\n+\t}\n+\n+\tif (config.stitchEnable) {\n+\t\t/* Add stitch.status metadata. */\n+\t\tStitchStatus stitchStatus;\n+\n+\t\tstitchStatus.diffPower = config.diffPower;\n+\t\tstitchStatus.motionThreshold = config.motionThreshold;\n+\t\tstitchStatus.thresholdLo = config.thresholdLo;\n+\n+\t\timageMetadata->set(\"stitch.status\", stitchStatus);\n+\t}\n+}\n+\n+/* Register algorithm with the system. */\n+static Algorithm *create(Controller *controller)\n+{\n+\treturn (Algorithm *)new Hdr(controller);\n+}\n+static RegisterAlgorithm reg(NAME, &create);\ndiff --git a/src/ipa/rpi/controller/rpi/hdr.h b/src/ipa/rpi/controller/rpi/hdr.h\nnew file mode 100644\nindex 000000000000..01ba45f1d3dc\n--- /dev/null\n+++ b/src/ipa/rpi/controller/rpi/hdr.h\n@@ -0,0 +1,72 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2023, Raspberry Pi Ltd\n+ *\n+ * hdr.h - HDR control algorithm\n+ */\n+#pragma once\n+\n+#include <map>\n+#include <string>\n+#include <vector>\n+\n+#include \"../hdr_algorithm.h\"\n+#include \"../hdr_status.h\"\n+#include \"../pwl.h\"\n+\n+/* This is our implementation of an HDR algorithm. */\n+\n+namespace RPiController {\n+\n+struct HdrConfig {\n+\tstd::string name;\n+\tstd::vector<unsigned int> cadence;\n+\tstd::map<unsigned int, std::string> channelMap;\n+\n+\t/* Tonemap related parameters. */\n+\tbool tonemapEnable;\n+\tuint16_t detailConstant;\n+\tdouble detailSlope;\n+\tdouble iirStrength;\n+\tdouble strength;\n+\t/* We must have either an explicit tonemap curve, or the other parameters. */\n+\tPwl tonemap;\n+\tPwl target; /* maps histogram quatile to desired target output value */\n+\tPwl maxSlope; /* the maximum slope allowed at each point in the mapping */\n+\tdouble minSlope; /* the minimum allowed slope */\n+\tdouble maxGain; /* limit to the max absolute gain */\n+\tdouble step; /* the histogram granularity for building the mapping */\n+\tdouble speed; /* rate at which tonemap is updated */\n+\n+\t/* Stitch related parameters. */\n+\tbool stitchEnable;\n+\tuint16_t thresholdLo;\n+\tuint8_t diffPower;\n+\tdouble motionThreshold;\n+\n+\tvoid read(const libcamera::YamlObject &params, const std::string &name);\n+};\n+\n+class Hdr : public HdrAlgorithm\n+{\n+public:\n+\tHdr(Controller *controller);\n+\tchar const *name() const override;\n+\tvoid switchMode(CameraMode const &cameraMode, Metadata *metadata) override;\n+\tint read(const libcamera::YamlObject &params) override;\n+\tvoid process(StatisticsPtr &stats, Metadata *imageMetadata) override;\n+\tint setMode(std::string const &mode) override;\n+\tstd::vector<unsigned int> getChannels() const override;\n+\n+private:\n+\tvoid updateAgcStatus(Metadata *metadata);\n+\tbool updateTonemap(StatisticsPtr &stats, HdrConfig &config);\n+\n+\tstd::map<std::string, HdrConfig> config_;\n+\tHdrStatus status_; /* track the current HDR mode and channel */\n+\tHdrStatus delayedStatus_; /* track the delayed HDR mode and channel */\n+\tstd::string previousMode_;\n+\tPwl tonemap_;\n+};\n+\n+} /* namespace RPiController */\ndiff --git a/src/ipa/rpi/controller/rpi/saturation.cpp b/src/ipa/rpi/controller/rpi/saturation.cpp\nnew file mode 100644\nindex 000000000000..813540e5154d\n--- /dev/null\n+++ b/src/ipa/rpi/controller/rpi/saturation.cpp\n@@ -0,0 +1,57 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2022 Raspberry Pi Ltd\n+ *\n+ * saturation.cpp - Saturation control algorithm\n+ */\n+#include \"saturation.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include \"saturation_status.h\"\n+\n+using namespace RPiController;\n+using namespace libcamera;\n+\n+LOG_DEFINE_CATEGORY(RPiSaturation)\n+\n+#define NAME \"rpi.saturation\"\n+\n+Saturation::Saturation(Controller *controller)\n+\t: Algorithm(controller)\n+{\n+}\n+\n+char const *Saturation::name() const\n+{\n+\treturn NAME;\n+}\n+\n+int Saturation::read(const libcamera::YamlObject &params)\n+{\n+\tconfig_.shiftR = params[\"shift_r\"].get<uint8_t>(0);\n+\tconfig_.shiftG = params[\"shift_g\"].get<uint8_t>(0);\n+\tconfig_.shiftB = params[\"shift_b\"].get<uint8_t>(0);\n+\treturn 0;\n+}\n+\n+void Saturation::initialise()\n+{\n+}\n+\n+void Saturation::prepare(Metadata *imageMetadata)\n+{\n+\tSaturationStatus saturation;\n+\n+\tsaturation.shiftR = config_.shiftR;\n+\tsaturation.shiftG = config_.shiftG;\n+\tsaturation.shiftB = config_.shiftB;\n+\timageMetadata->set(\"saturation.status\", saturation);\n+}\n+\n+// Register algorithm with the system.\n+static Algorithm *Create(Controller *controller)\n+{\n+\treturn (Algorithm *)new Saturation(controller);\n+}\n+static RegisterAlgorithm reg(NAME, &Create);\ndiff --git a/src/ipa/rpi/controller/rpi/saturation.h b/src/ipa/rpi/controller/rpi/saturation.h\nnew file mode 100644\nindex 000000000000..97da412ad59a\n--- /dev/null\n+++ b/src/ipa/rpi/controller/rpi/saturation.h\n@@ -0,0 +1,32 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2022, Raspberry Pi Ltd\n+ *\n+ * saturation.hpp - Saturation control algorithm\n+ */\n+#pragma once\n+\n+#include \"algorithm.h\"\n+\n+namespace RPiController {\n+\n+struct SaturationConfig {\n+\tuint8_t shiftR;\n+\tuint8_t shiftG;\n+\tuint8_t shiftB;\n+};\n+\n+class Saturation : public Algorithm\n+{\n+public:\n+\tSaturation(Controller *controller = NULL);\n+\tchar const *name() const override;\n+\tint read(const libcamera::YamlObject &params) override;\n+\tvoid initialise() override;\n+\tvoid prepare(Metadata *imageMetadata) override;\n+\n+private:\n+\tSaturationConfig config_;\n+};\n+\n+} // namespace RPiController\ndiff --git a/src/ipa/rpi/controller/rpi/sdn.cpp b/src/ipa/rpi/controller/rpi/sdn.cpp\nindex b6b662518f2c..6743919e6b36 100644\n--- a/src/ipa/rpi/controller/rpi/sdn.cpp\n+++ b/src/ipa/rpi/controller/rpi/sdn.cpp\n@@ -36,6 +36,8 @@ char const *Sdn::name() const\n \n int Sdn::read(const libcamera::YamlObject &params)\n {\n+\tLOG(RPiSdn, Warning)\n+\t\t<< \"Using legacy SDN tuning - please consider moving SDN inside rpi.denoise\";\n \tdeviation_ = params[\"deviation\"].get<double>(3.2);\n \tstrength_ = params[\"strength\"].get<double>(0.75);\n \treturn 0;\ndiff --git a/src/ipa/rpi/controller/rpi/tonemap.cpp b/src/ipa/rpi/controller/rpi/tonemap.cpp\nnew file mode 100644\nindex 000000000000..5f8b2bf25aeb\n--- /dev/null\n+++ b/src/ipa/rpi/controller/rpi/tonemap.cpp\n@@ -0,0 +1,61 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2022 Raspberry Pi Ltd\n+ *\n+ * tonemap.cpp - Tonemap control algorithm\n+ */\n+#include \"tonemap.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include \"tonemap_status.h\"\n+\n+using namespace RPiController;\n+using namespace libcamera;\n+\n+LOG_DEFINE_CATEGORY(RPiTonemap)\n+\n+#define NAME \"rpi.tonemap\"\n+\n+Tonemap::Tonemap(Controller *controller)\n+\t: Algorithm(controller)\n+{\n+}\n+\n+char const *Tonemap::name() const\n+{\n+\treturn NAME;\n+}\n+\n+int Tonemap::read(const libcamera::YamlObject &params)\n+{\n+\tconfig_.detailConstant = params[\"detail_constant\"].get<uint16_t>(0);\n+\tconfig_.detailSlope = params[\"detail_slope\"].get<double>(0.1);\n+\tconfig_.iirStrength = params[\"iir_strength\"].get<double>(1.0);\n+\tconfig_.strength = params[\"strength\"].get<double>(1.0);\n+\tconfig_.tonemap.read(params[\"tone_curve\"]);\n+\treturn 0;\n+}\n+\n+void Tonemap::initialise()\n+{\n+}\n+\n+void Tonemap::prepare(Metadata *imageMetadata)\n+{\n+\tTonemapStatus tonemapStatus;\n+\n+\ttonemapStatus.detailConstant = config_.detailConstant;\n+\ttonemapStatus.detailSlope = config_.detailSlope;\n+\ttonemapStatus.iirStrength = config_.iirStrength;\n+\ttonemapStatus.strength = config_.strength;\n+\ttonemapStatus.tonemap = config_.tonemap;\n+\timageMetadata->set(\"tonemap.status\", tonemapStatus);\n+}\n+\n+// Register algorithm with the system.\n+static Algorithm *Create(Controller *controller)\n+{\n+\treturn (Algorithm *)new Tonemap(controller);\n+}\n+static RegisterAlgorithm reg(NAME, &Create);\ndiff --git a/src/ipa/rpi/controller/rpi/tonemap.h b/src/ipa/rpi/controller/rpi/tonemap.h\nnew file mode 100644\nindex 000000000000..f25aa47f86c2\n--- /dev/null\n+++ b/src/ipa/rpi/controller/rpi/tonemap.h\n@@ -0,0 +1,35 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2022, Raspberry Pi Ltd\n+ *\n+ * tonemap.hpp - Tonemap control algorithm\n+ */\n+#pragma once\n+\n+#include \"algorithm.h\"\n+#include \"pwl.h\"\n+\n+namespace RPiController {\n+\n+struct TonemapConfig {\n+\tuint16_t detailConstant;\n+\tdouble detailSlope;\n+\tdouble iirStrength;\n+\tdouble strength;\n+\tPwl tonemap;\n+};\n+\n+class Tonemap : public Algorithm\n+{\n+public:\n+\tTonemap(Controller *controller = NULL);\n+\tchar const *name() const override;\n+\tint read(const libcamera::YamlObject &params) override;\n+\tvoid initialise() override;\n+\tvoid prepare(Metadata *imageMetadata) override;\n+\n+private:\n+\tTonemapConfig config_;\n+};\n+\n+} // namespace RPiController\ndiff --git a/src/ipa/rpi/controller/saturation_status.h b/src/ipa/rpi/controller/saturation_status.h\nnew file mode 100644\nindex 000000000000..337b66a3e91e\n--- /dev/null\n+++ b/src/ipa/rpi/controller/saturation_status.h\n@@ -0,0 +1,13 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2022 Raspberry Pi Ltd\n+ *\n+ * saturation_status.h - Saturation control algorithm status\n+ */\n+#pragma once\n+\n+struct SaturationStatus {\n+\tuint8_t shiftR;\n+\tuint8_t shiftG;\n+\tuint8_t shiftB;\n+};\ndiff --git a/src/ipa/rpi/controller/stitch_status.h b/src/ipa/rpi/controller/stitch_status.h\nnew file mode 100644\nindex 000000000000..b17800ed6697\n--- /dev/null\n+++ b/src/ipa/rpi/controller/stitch_status.h\n@@ -0,0 +1,17 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2023 Raspberry Pi Ltd\n+ *\n+ * stitch_status.h - stitch control algorithm status\n+ */\n+#pragma once\n+\n+/*\n+ * Parameters for the stitch block.\n+ */\n+\n+struct StitchStatus {\n+\tuint16_t thresholdLo;\n+\tuint8_t diffPower;\n+\tdouble motionThreshold;\n+};\ndiff --git a/src/ipa/rpi/controller/tonemap_status.h b/src/ipa/rpi/controller/tonemap_status.h\nnew file mode 100644\nindex 000000000000..0e6399467869\n--- /dev/null\n+++ b/src/ipa/rpi/controller/tonemap_status.h\n@@ -0,0 +1,17 @@\n+/* SPDX-License-Identifier: BSD-2-Clause */\n+/*\n+ * Copyright (C) 2022 Raspberry Pi Ltd\n+ *\n+ * hdr.h - Tonemap control algorithm status\n+ */\n+#pragma once\n+\n+#include \"pwl.h\"\n+\n+struct TonemapStatus {\n+\tuint16_t detailConstant;\n+\tdouble detailSlope;\n+\tdouble iirStrength;\n+\tdouble strength;\n+\tRPiController::Pwl tonemap;\n+};\n","prefixes":["libcamera-devel","v2","11/20"]}