Show a patch.

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

{
    "id": 23377,
    "url": "https://patchwork.libcamera.org/api/patches/23377/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/23377/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/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": "<20250515160432.119022-2-mzamazal@redhat.com>",
    "date": "2025-05-15T16:04:31",
    "name": "[v3,1/1] libcamera: software_isp: Add saturation control",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "2690e87effe967de6a77a05d6448424bff805479",
    "submitter": {
        "id": 177,
        "url": "https://patchwork.libcamera.org/api/people/177/?format=api",
        "name": "Milan Zamazal",
        "email": "mzamazal@redhat.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/23377/mbox/",
    "series": [
        {
            "id": 5175,
            "url": "https://patchwork.libcamera.org/api/series/5175/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5175",
            "date": "2025-05-15T16:04:30",
            "name": "Add saturation control to software ISP",
            "version": 3,
            "mbox": "https://patchwork.libcamera.org/series/5175/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/23377/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/23377/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 8CD1AC3220\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 15 May 2025 16:04:50 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 43DAE68B6F;\n\tThu, 15 May 2025 18:04:50 +0200 (CEST)",
            "from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.133.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C4DB168B4F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 15 May 2025 18:04:47 +0200 (CEST)",
            "from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com\n\t(ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63])\n\tby relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n\tcipher=TLS_AES_256_GCM_SHA384) id us-mta-610-kx5F9P05MCS10Dr3LSN1YA-1;\n\tThu, 15 May 2025 12:04:43 -0400",
            "from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com\n\t(mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com\n\t[10.30.177.40])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\tkey-exchange X25519 server-signature RSA-PSS (2048 bits)\n\tserver-digest SHA256) (No client certificate requested)\n\tby mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTPS id 1DB921955DAD; Thu, 15 May 2025 16:04:42 +0000 (UTC)",
            "from mzamazal-thinkpadp1gen7.tpbc.com (unknown [10.44.32.124])\n\tby mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTP id 2D85B1956066; Thu, 15 May 2025 16:04:39 +0000 (UTC)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"YZ2/exj4\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1747325086;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tcontent-transfer-encoding:content-transfer-encoding:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=E8rqiQAHcdJ8edqF8tKFSbZIleKMy9ZrenykOPkVZis=;\n\tb=YZ2/exj4F7I4TNArFHZrjUSznqdZKCSJnYenSisFn3CUyazLsBBtIYxDx3ZFqKpHWlUir2\n\tbWAFLHevyoPTS2HOxocqiTuhAedzG7PJuqlaWkK3z+BDx1kuqiSUcbyCjJhsSIC5BV/mBT\n\tkSAQnNnhMcATtgFpWXvSGEzJagpkM6A=",
        "X-MC-Unique": "kx5F9P05MCS10Dr3LSN1YA-1",
        "X-Mimecast-MFC-AGG-ID": "kx5F9P05MCS10Dr3LSN1YA_1747325082",
        "From": "Milan Zamazal <mzamazal@redhat.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Milan Zamazal <mzamazal@redhat.com>,\n\tPaul Elder <paul.elder@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>",
        "Subject": "[PATCH v3 1/1] libcamera: software_isp: Add saturation control",
        "Date": "Thu, 15 May 2025 18:04:31 +0200",
        "Message-ID": "<20250515160432.119022-2-mzamazal@redhat.com>",
        "In-Reply-To": "<20250515160432.119022-1-mzamazal@redhat.com>",
        "References": "<20250515160432.119022-1-mzamazal@redhat.com>",
        "MIME-Version": "1.0",
        "X-Scanned-By": "MIMEDefang 3.0 on 10.30.177.40",
        "X-Mimecast-Spam-Score": "0",
        "X-Mimecast-MFC-PROC-ID": "1nwrRwEemyeGqjLi1vDtQG4_VxuC-Ukfab4hXAVg_bQ_1747325082",
        "X-Mimecast-Originator": "redhat.com",
        "Content-Transfer-Encoding": "8bit",
        "content-type": "text/plain; charset=\"US-ASCII\"; x-default=true",
        "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": "Saturation control is added on top of the colour correction matrix.  A\nmethod of saturation adjustment that can be fully integrated into the\ncolour correction matrix is used.  The control is available only if Ccm\nalgorithm is enabled.\n\nThe control uses 0.0-2.0 value range, with 1.0 being unmodified\nsaturation, 0.0 full desaturation and 2.0 quite saturated.\n\nThe saturation is adjusted by converting to Y'CbCr colour space,\napplying the saturation value on the colour axes, and converting back to\nRGB.  ITU-R BT.601 conversion is used to convert between the colour\nspaces, for no particular reason.\n\nThe colour correction matrix is applied before gamma and the given\nmatrix is suitable for such a case.  Alternatively, the transformation\nused in libcamera rpi ccm.cpp could be used.\n\nSigned-off-by: Milan Zamazal <mzamazal@redhat.com>\n---\n src/ipa/simple/algorithms/ccm.cpp | 60 +++++++++++++++++++++++++++++--\n src/ipa/simple/algorithms/ccm.h   | 11 ++++++\n src/ipa/simple/ipa_context.h      |  4 +++\n 3 files changed, 72 insertions(+), 3 deletions(-)",
    "diff": "diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\nindex d5ba928d..0a98406c 100644\n--- a/src/ipa/simple/algorithms/ccm.cpp\n+++ b/src/ipa/simple/algorithms/ccm.cpp\n@@ -3,7 +3,7 @@\n  * Copyright (C) 2024, Ideas On Board\n  * Copyright (C) 2024-2025, Red Hat Inc.\n  *\n- * Color correction matrix\n+ * Color correction matrix + saturation\n  */\n \n #include \"ccm.h\"\n@@ -13,6 +13,8 @@\n \n #include <libcamera/control_ids.h>\n \n+#include \"libcamera/internal/matrix.h\"\n+\n namespace {\n \n constexpr unsigned int kTemperatureThreshold = 100;\n@@ -35,28 +37,77 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData\n \t}\n \n \tcontext.ccmEnabled = true;\n+\tcontext.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);\n+\n+\treturn 0;\n+}\n+\n+int Ccm::configure(IPAContext &context,\n+\t\t   [[maybe_unused]] const IPAConfigInfo &configInfo)\n+{\n+\tcontext.activeState.knobs.saturation = std::optional<double>();\n \n \treturn 0;\n }\n \n+void Ccm::queueRequest(typename Module::Context &context,\n+\t\t       [[maybe_unused]] const uint32_t frame,\n+\t\t       [[maybe_unused]] typename Module::FrameContext &frameContext,\n+\t\t       const ControlList &controls)\n+{\n+\tconst auto &saturation = controls.get(controls::Saturation);\n+\tif (saturation.has_value()) {\n+\t\tcontext.activeState.knobs.saturation = saturation;\n+\t\tLOG(IPASoftCcm, Debug) << \"Setting saturation to \" << saturation.value();\n+\t}\n+}\n+\n+void Ccm::applySaturation(Matrix<float, 3, 3> &ccm, float saturation)\n+{\n+\t/* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */\n+\tconst Matrix<float, 3, 3> rgb2ycbcr{\n+\t\t{ 0.256788235294, 0.504129411765, 0.0979058823529,\n+\t\t  -0.148223529412, -0.290992156863, 0.439215686275,\n+\t\t  0.439215686275, -0.367788235294, -0.0714274509804 }\n+\t};\n+\tconst Matrix<float, 3, 3> ycbcr2rgb{\n+\t\t{ 1.16438356164, 0, 1.59602678571,\n+\t\t  1.16438356164, -0.391762290094, -0.812967647235,\n+\t\t  1.16438356164, 2.01723214285, 0 }\n+\t};\n+\tconst Matrix<float, 3, 3> saturationMatrix{\n+\t\t{ 1, 0, 0,\n+\t\t  0, saturation, 0,\n+\t\t  0, 0, saturation }\n+\t};\n+\tccm = ycbcr2rgb * saturationMatrix * rgb2ycbcr * ccm;\n+}\n+\n void Ccm::prepare(IPAContext &context, const uint32_t frame,\n \t\t  IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params)\n {\n+\tauto &saturation = context.activeState.knobs.saturation;\n+\n \tconst unsigned int ct = context.activeState.awb.temperatureK;\n \n-\t/* Change CCM only on bigger temperature changes. */\n+\t/* Change CCM only on saturation or bigger temperature changes. */\n \tif (frame > 0 &&\n-\t    utils::abs_diff(ct, lastCt_) < kTemperatureThreshold) {\n+\t    utils::abs_diff(ct, lastCt_) < kTemperatureThreshold &&\n+\t    saturation == lastSaturation_) {\n \t\tframeContext.ccm.ccm = context.activeState.ccm.ccm;\n \t\tcontext.activeState.ccm.changed = false;\n \t\treturn;\n \t}\n \n \tlastCt_ = ct;\n+\tlastSaturation_ = saturation;\n \tMatrix<float, 3, 3> ccm = ccm_.getInterpolated(ct);\n+\tif (saturation)\n+\t\tapplySaturation(ccm, saturation.value());\n \n \tcontext.activeState.ccm.ccm = ccm;\n \tframeContext.ccm.ccm = ccm;\n+\tframeContext.saturation = saturation;\n \tcontext.activeState.ccm.changed = true;\n }\n \n@@ -67,6 +118,9 @@ void Ccm::process([[maybe_unused]] IPAContext &context,\n \t\t  ControlList &metadata)\n {\n \tmetadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data());\n+\n+\tconst auto &saturation = frameContext.saturation;\n+\tmetadata.set(controls::Saturation, saturation.value_or(1.0));\n }\n \n REGISTER_IPA_ALGORITHM(Ccm, \"Ccm\")\ndiff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h\nindex f4e2b85b..8279a3d5 100644\n--- a/src/ipa/simple/algorithms/ccm.h\n+++ b/src/ipa/simple/algorithms/ccm.h\n@@ -7,6 +7,8 @@\n \n #pragma once\n \n+#include <optional>\n+\n #include \"libcamera/internal/matrix.h\"\n \n #include <libipa/interpolator.h>\n@@ -24,6 +26,12 @@ public:\n \t~Ccm() = default;\n \n \tint init(IPAContext &context, const YamlObject &tuningData) override;\n+\tint configure(IPAContext &context,\n+\t\t      const IPAConfigInfo &configInfo) override;\n+\tvoid queueRequest(typename Module::Context &context,\n+\t\t\t  const uint32_t frame,\n+\t\t\t  typename Module::FrameContext &frameContext,\n+\t\t\t  const ControlList &controls) override;\n \tvoid prepare(IPAContext &context,\n \t\t     const uint32_t frame,\n \t\t     IPAFrameContext &frameContext,\n@@ -34,7 +42,10 @@ public:\n \t\t     ControlList &metadata) override;\n \n private:\n+\tvoid applySaturation(Matrix<float, 3, 3> &ccm, float saturation);\n+\n \tunsigned int lastCt_;\n+\tstd::optional<float> lastSaturation_;\n \tInterpolator<Matrix<float, 3, 3>> ccm_;\n };\n \ndiff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\nindex 88cc6c35..a471b80a 100644\n--- a/src/ipa/simple/ipa_context.h\n+++ b/src/ipa/simple/ipa_context.h\n@@ -63,6 +63,7 @@ struct IPAActiveState {\n \tstruct {\n \t\t/* 0..2 range, 1.0 = normal */\n \t\tstd::optional<double> contrast;\n+\t\tstd::optional<float> saturation;\n \t} knobs;\n };\n \n@@ -75,11 +76,14 @@ struct IPAFrameContext : public FrameContext {\n \t\tint32_t exposure;\n \t\tdouble gain;\n \t} sensor;\n+\n \tstruct {\n \t\tdouble red;\n \t\tdouble blue;\n \t} gains;\n+\n \tstd::optional<double> contrast;\n+\tstd::optional<float> saturation;\n };\n \n struct IPAContext {\n",
    "prefixes": [
        "v3",
        "1/1"
    ]
}