[{"id":34174,"web_url":"https://patchwork.libcamera.org/comment/34174/","msgid":"<078827fb-dd65-46d2-bfc0-77a48edbe85a@linaro.org>","date":"2025-05-10T16:29:57","subject":"Re: [PATCH v2 1/1] libcamera: software_isp: Add saturation control","submitter":{"id":175,"url":"https://patchwork.libcamera.org/api/people/175/","name":"Bryan O'Donoghue","email":"bryan.odonoghue@linaro.org"},"content":"On 30/04/2025 12:20, Milan Zamazal wrote:\n> Saturation control is added on top of the colour correction matrix.  A\n> way of saturation adjustment that can be fully integrated into the\n> colour correction matrix is used.  The control is available only if Ccm\n> algorithm is enabled.\n> \n> The control uses 0.0-2.0 value range, with 1.0 being unmodified\n> saturation, 0.0 full desaturation and 2.0 quite saturated.\n> \n> The saturation is adjusted by converting to Y'CbCr colour space,\n> applying the saturation value on the colour axes, and converting back to\n> RGB.  ITU-R BT.601 conversion is used to convert between the colour\n> spaces, for no particular reason.\n> \n> The colour correction matrix is applied before gamma and the given\n> matrix is suitable for such a case.  Alternatively, the transformation\n> used in libcamera rpi ccm.cpp could be used.\n> \n> Signed-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(-)\n> \n> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\n> index d5ba928d..021aefc9 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::updateSaturation(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> +I applied this to the GPUISP branch but it occurs to me I don't really \nknow how to test it.\n\nhttps://gitlab.freedesktop.org/camera/libcamera-softisp/-/tree/origin-master+whole-frame-swtats-v2-gpuisp-v1?ref_type=heads\n\n---\nbod","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 56C0CC3200\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSat, 10 May 2025 16:30:02 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4B77D68B5A;\n\tSat, 10 May 2025 18:30:01 +0200 (CEST)","from mail-wm1-x32b.google.com (mail-wm1-x32b.google.com\n\t[IPv6:2a00:1450:4864:20::32b])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B3BD568B4F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 10 May 2025 18:29:59 +0200 (CEST)","by mail-wm1-x32b.google.com with SMTP id\n\t5b1f17b1804b1-43cfecdd8b2so22222785e9.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 10 May 2025 09:29:59 -0700 (PDT)","from [192.168.0.35] (188-141-3-146.dynamic.upc.ie. [188.141.3.146])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-442d6858579sm66354255e9.28.2025.05.10.09.29.58\n\t(version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\n\tSat, 10 May 2025 09:29:58 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=linaro.org header.i=@linaro.org\n\theader.b=\"t/ig6J2G\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=linaro.org; s=google; t=1746894599; x=1747499399;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:in-reply-to:from:content-language\n\t:references:cc:to:subject:user-agent:mime-version:date:message-id\n\t:from:to:cc:subject:date:message-id:reply-to;\n\tbh=rx7X2ZMtIkBKumAJYA3jzo7P0+0IXNYui5WuFZDMB90=;\n\tb=t/ig6J2G7MAWKOuxhgPg+CuYWpyrKULbHZRZdvGE0Ji3FNBO3IPvqruj1DR9jUhhwO\n\tl5DBE17BcwGZdKeJzbKuOoTgXLIKLL7SWf9xSYLcc/S0NtJfsl1JLOuT2S9P5Px6LV0+\n\t+JbHdAJlirnYDotAqvSJFqS4oMZguAM1gtlSTKRK82eloeP6gcJUkcbKP1/0FZs7Lzlb\n\tqWoNu8JkAhrJhGgp22M+hSVdMwNMxcmPIV9t4ASRO7tSk2k91ErRi22+2KzvQ5utomzS\n\ti1DgGih8c0RTYK3B84CZh+qPwN02MZ/6Pw1+izjIxOK+Q8IBY4TXmDARkZVpQtlYJm+g\n\t1mDg==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1746894599; x=1747499399;\n\th=content-transfer-encoding:in-reply-to:from:content-language\n\t:references:cc:to:subject:user-agent:mime-version:date:message-id\n\t:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;\n\tbh=rx7X2ZMtIkBKumAJYA3jzo7P0+0IXNYui5WuFZDMB90=;\n\tb=lPNczDvPIBM2Exg6w8GI6fRs9kKfhYsEMkilsa9A6JvWrEIFNmzPnccv6FwD+bR4Qh\n\tTAXysrRmPZPbPAtyb+hmxGlLPlpwf2LK6XfXHKVqPJiPivJ0s3RVqGEZOvvxdioNRl2U\n\t3TtyD+J+4zoWnzRRRoO+RpuPHlVSoVflP2Z40ShLj2n9jdedm+iAZkrmr/eqN1Yw9tav\n\tFi5bHrqj0NNyLEOP6+QUBS3mFSaTNdS67t0aUcs3W62cQYbmhJa7bjla25V2Mtxb7imx\n\th+CMc6F+XdfLBMadLhV/MCxq/916j6E8roixiWlT9l8rgfQsg2zSLIJmOKxEN9ZSfeAN\n\t+f/g==","X-Forwarded-Encrypted":"i=1;\n\tAJvYcCWhL5qxsfTx5wcQZvqNNEqmGycFZXfwJ9C5oK2tdP9G/MtODrBxvZxQhtVR6HUwjUZvc655QgC8vjScwar8idA=@lists.libcamera.org","X-Gm-Message-State":"AOJu0YydRK9BD76e9v9t+997X/0A8ga49oC0zuKj1KmsiyWxiv+cLUUO\n\t4XISGUbtSlvxNRSkN4Bf71m5K8jAWQ6LC9lqbonEyesPW3+TYVQwklnup2NuZNKgzFbY+1Y9r3O\n\tNXlM=","X-Gm-Gg":"ASbGncv+g53aaduP2RuBE3GDyxVf+KxAlWsagQpylVynUZPIIsonUBRbPDBUuqq948x\n\tgDjEZpOd6Wfwyirn74vpefzFg17qldQGOEw+LB92NJelbxlq2BAHv2OqIvCnx5vJ44CA2kYWPuO\n\tuQtRuCvvC2o5FFjcLX2wOGomfEilFuRa0DksppOzerg5MyzB34h6/uVre010vi8tx53QUdkNxZ3\n\t2nYyWRlVBah4IljgtULwJKcS7U+rFgi7YNED+TDbb7kyI11LXSj8ZiPjpVXluC5De7aV/huiJ8a\n\t+XEl4b3o6DNkt686+gDpJ7RsdotB3rDc/Em/oe0UQoaoI+dY0aA0Ol+Muo4f4BbRrunrfRAp+7w\n\tp5qf7oU/AQVOPKSSH","X-Google-Smtp-Source":"AGHT+IFbuEFMvhi6wDjkKN4xCiTIGx8oM0sIFfjWFsnA2l1D3GuBav5iPEkTdytw9EJ2Xt0AUo++Wg==","X-Received":"by 2002:a05:600c:1ca3:b0:442:ccf0:41e6 with SMTP id\n\t5b1f17b1804b1-442d6cf2e62mr67971505e9.3.1746894599049; \n\tSat, 10 May 2025 09:29:59 -0700 (PDT)","Message-ID":"<078827fb-dd65-46d2-bfc0-77a48edbe85a@linaro.org>","Date":"Sat, 10 May 2025 17:29:57 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v2 1/1] libcamera: software_isp: Add saturation control","To":"Milan Zamazal <mzamazal@redhat.com>, libcamera-devel@lists.libcamera.org","Cc":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>","References":"<20250430112043.23807-1-mzamazal@redhat.com>\n\t<wfecTn11KMgTFW0QfPxMYlB21TIpEmYsYHh8qdOlWqdYB9YOfXXzDfAGnA5PpB0eElJJ55vHIuOFapD17YtF8A==@protonmail.internalid>\n\t<20250430112043.23807-2-mzamazal@redhat.com>","Content-Language":"en-US","From":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","In-Reply-To":"<20250430112043.23807-2-mzamazal@redhat.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":34204,"web_url":"https://patchwork.libcamera.org/comment/34204/","msgid":"<85h61ppxor.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-05-12T17:04:52","subject":"Re: [PATCH v2 1/1] libcamera: software_isp: Add saturation control","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Bryan O'Donoghue <bryan.odonoghue@linaro.org> writes:\n\n> On 30/04/2025 12:20, Milan Zamazal wrote:\n>> Saturation control is added on top of the colour correction matrix.  A\n>> way of saturation adjustment that can be fully integrated into the\n>\n>> colour correction matrix is used.  The control is available only if Ccm\n>> algorithm is enabled.\n>> The control uses 0.0-2.0 value range, with 1.0 being unmodified\n>> saturation, 0.0 full desaturation and 2.0 quite saturated.\n>> The saturation is adjusted by converting to Y'CbCr colour space,\n>> applying the saturation value on the colour axes, and converting back to\n>> RGB.  ITU-R BT.601 conversion is used to convert between the colour\n>> spaces, for no particular reason.\n>> The colour correction matrix is applied before gamma and the given\n>> matrix is suitable for such a case.  Alternatively, the transformation\n>> used in libcamera rpi ccm.cpp could be used.\n>> Signed-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(-)\n>> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\n>> index d5ba928d..021aefc9 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>>   #include \"ccm.h\"\n>> @@ -13,6 +13,8 @@\n>>   #include <libcamera/control_ids.h>\n>> +#include \"libcamera/internal/matrix.h\"\n>> +\n>>   namespace {\n>>   constexpr unsigned int kTemperatureThreshold = 100;\n>> @@ -35,28 +37,77 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData\n>>   \t}\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>>   \treturn 0;\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::updateSaturation(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>> +I applied this to the GPUISP branch but it occurs to me I don't really \n> know how to test it.\n\nIf CCM is already working, you can run it with camshark and play with\nthe saturation control there: 0.0 = fully desaturated, 1.0 = normal\nsaturation, 2.0 = quite saturated.\n\n> https://gitlab.freedesktop.org/camera/libcamera-softisp/-/tree/origin-master+whole-frame-swtats-v2-gpuisp-v1?ref_type=heads\n>\n> ---\n> bod","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 8C5B2C3226\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 12 May 2025 17:05:02 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 75C3D617C7;\n\tMon, 12 May 2025 19:05:01 +0200 (CEST)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.129.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7C812614E3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 12 May 2025 19:04:59 +0200 (CEST)","from mail-ed1-f72.google.com (mail-ed1-f72.google.com\n\t[209.85.208.72]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-207-MKDc0DWANcCPNuc5Ow_Wuw-1; Mon, 12 May 2025 13:04:56 -0400","by mail-ed1-f72.google.com with SMTP id\n\t4fb4d7f45d1cf-5fc812d9cf8so4666517a12.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 12 May 2025 10:04:56 -0700 (PDT)","from mzamazal-thinkpadp1gen7.tpbc.csb\n\t(ip-77-48-47-2.net.vodafone.cz. [77.48.47.2])\n\tby smtp.gmail.com with ESMTPSA id\n\t4fb4d7f45d1cf-5fc9d70dec2sm5912833a12.71.2025.05.12.10.04.52\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 12 May 2025 10:04:53 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"KWcbg0nd\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1747069498;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=oQh8jXhIEDXEqupNnJGThaIAKoW1bJckZctdpVLG5Vc=;\n\tb=KWcbg0nd2uJS8eK7w4NVNxw+MfGgrpQis0O1IskgAXLslD3ip+nqYwCAVn+AJSQraLZSlG\n\tVjvP7zv1ETBI8pLLOZJ/Q7dUBlpF0RPYZGK3bsQK9FqBcpbW5G/6SG7+1fz8Zb5qM8MYOb\n\tSNbkTanP5P3BntXBLtOmmXK32/zigiE=","X-MC-Unique":"MKDc0DWANcCPNuc5Ow_Wuw-1","X-Mimecast-MFC-AGG-ID":"MKDc0DWANcCPNuc5Ow_Wuw_1747069495","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1747069495; x=1747674295;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=oQh8jXhIEDXEqupNnJGThaIAKoW1bJckZctdpVLG5Vc=;\n\tb=hpJScw6wyDhB0hVdkEIsmgOPmliMpfxL8EmzArDjaMt9jEfyU0gTxuaejMVIEhKWgu\n\t/ELfbFFi8Vji4+mdc9vTuvXRnQH+9N2uUPpZ/EHl9tPvTggvhlGcks3Zwe+vl/gCKfV0\n\tBwBlNwIovNBp6sAqKFPLjEfKFUJmV1pYJVXXEbRi5NxZvJFdR54mbVQV/x/0jZppsDdZ\n\tv6wLSgnRZPgdds/WyYggu1P1wz2qclrtWcDq0QCYuYi2Mrlqp2OfHo6IL5UUsfyLWxZs\n\tKIujucsGDfm1xLmwXP75P9R3k7yFdMyev/ffrKCRBpW2w4Cfh/U+meKRvznn4hB+zkk2\n\tNIrg==","X-Gm-Message-State":"AOJu0YyocMpsSgmcM9SO5KETX/vUw6cvhEpPETmAeBE6Efmek2AaYbk1\n\tGcx5JFac1gSZdHEryINm9RBwg7ZRYyIEw0ZgwtMW3AJMi4bCJqkl3HgWx4VGJaPG/UrrjwGEsEo\n\tBJTjb0rh665Hf5YnNDg66+7hz6oA1hSlXwtPxO36VIymL8BLXLmVEbqgycZs3l43B7qLQJPAN5y\n\tdoJlTNjOYmx7I9coyqm62UB20eue9biUuwJPRMzVrva1JweO241ZSpZ4Q=","X-Gm-Gg":"ASbGncu1F77r537HpJFgqn91GUDUBQq+xB92sW30Cgo/FaEAP2aokRRfXNC+zcNzBpP\n\tFfaIrTRWEAsKtTtVrELrfX2ZrnZMsRX3stm2ZSpeL1zjUU23v3CogZuyIrmzUOB05qMMhv9bHIa\n\tFdgkHj7QwDniUbWmnKtd+EeYkbOXMX9qsFdeT0wv4qu8Bfrs40UYo/G+KH98+bZmkY9iA7ZIbgM\n\tDgyjnm7BFrnyheCja18dV3OTdvZm5l0/xChF797XjOEs6XHuCu0nZZ9pLCItQ55T2Uffv6sHR+G\n\tatlh7NbqvZeXiKk9PruKwWpQxwnqbnlSrryPoZBDsDExfURRB+trNV2BvkhC5N8j","X-Received":["by 2002:a05:6402:1ece:b0:5fd:c426:9d17 with SMTP id\n\t4fb4d7f45d1cf-5fdc4269e38mr4784183a12.34.1747069494724; \n\tMon, 12 May 2025 10:04:54 -0700 (PDT)","by 2002:a05:6402:1ece:b0:5fd:c426:9d17 with SMTP id\n\t4fb4d7f45d1cf-5fdc4269e38mr4784138a12.34.1747069494195; \n\tMon, 12 May 2025 10:04:54 -0700 (PDT)"],"X-Google-Smtp-Source":"AGHT+IG279xoqmvmCd7J9pyUM6vC95XtAsnw3TAYkqKU1grnMbSFK1HvtQZeNaSJaezAw4tTrjVktg==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Bryan O'Donoghue <bryan.odonoghue@linaro.org>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH v2 1/1] libcamera: software_isp: Add saturation control","In-Reply-To":"<078827fb-dd65-46d2-bfc0-77a48edbe85a@linaro.org> (Bryan\n\tO'Donoghue's message of \"Sat, 10 May 2025 17:29:57 +0100\")","References":"<20250430112043.23807-1-mzamazal@redhat.com>\n\t<wfecTn11KMgTFW0QfPxMYlB21TIpEmYsYHh8qdOlWqdYB9YOfXXzDfAGnA5PpB0eElJJ55vHIuOFapD17YtF8A==@protonmail.internalid>\n\t<20250430112043.23807-2-mzamazal@redhat.com>\n\t<078827fb-dd65-46d2-bfc0-77a48edbe85a@linaro.org>","Date":"Mon, 12 May 2025 19:04:52 +0200","Message-ID":"<85h61ppxor.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"gEIFBm2MhpctfgfCSxAAvOeA67u4UyxO9amiBsWvuC0_1747069495","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":34226,"web_url":"https://patchwork.libcamera.org/comment/34226/","msgid":"<174714697240.233090.14766643513800311785@pyrite.rasen.tech>","date":"2025-05-13T14:36:12","subject":"Re: [PATCH v2 1/1] libcamera: software_isp: Add saturation control","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Hi Milan,\n\nThanks for the patch.\n\nQuoting Milan Zamazal (2025-04-30 13:20:42)\n> Saturation control is added on top of the colour correction matrix.  A\n> way of saturation adjustment that can be fully integrated into the\n\ns/way/method/ I think would be clearer (without having to reorganize the entire\nsentence)\n\n> colour correction matrix is used.  The control is available only if Ccm\n> algorithm is enabled.\n> \n> The control uses 0.0-2.0 value range, with 1.0 being unmodified\n> saturation, 0.0 full desaturation and 2.0 quite saturated.\n> \n> The saturation is adjusted by converting to Y'CbCr colour space,\n> applying the saturation value on the colour axes, and converting back to\n> RGB.  ITU-R BT.601 conversion is used to convert between the colour\n> spaces, for no particular reason.\n> \n> The colour correction matrix is applied before gamma and the given\n> matrix is suitable for such a case.  Alternatively, the transformation\n> used in libcamera rpi ccm.cpp could be used.\n> \n> Signed-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(-)\n> \n> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\n> index d5ba928d..021aefc9 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>         }\n>  \n>         context.ccmEnabled = true;\n> +       context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);\n> +\n> +       return 0;\n> +}\n> +\n> +int Ccm::configure(IPAContext &context,\n> +                  [[maybe_unused]] const IPAConfigInfo &configInfo)\n> +{\n> +       context.activeState.knobs.saturation = std::optional<double>();\n>  \n>         return 0;\n>  }\n>  \n> +void Ccm::queueRequest(typename Module::Context &context,\n> +                      [[maybe_unused]] const uint32_t frame,\n> +                      [[maybe_unused]] typename Module::FrameContext &frameContext,\n> +                      const ControlList &controls)\n> +{\n> +       const auto &saturation = controls.get(controls::Saturation);\n> +       if (saturation.has_value()) {\n> +               context.activeState.knobs.saturation = saturation;\n> +               LOG(IPASoftCcm, Debug) << \"Setting saturation to \" << saturation.value();\n> +       }\n> +}\n> +\n> +void Ccm::updateSaturation(Matrix<float, 3, 3> &ccm, float saturation)\n\nI think applySaturation might be a more appropriate name. updateSaturation\nsounds like saturation is the output value and it gets updated based on some\ninput.\n\n> +{\n> +       /* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */\n> +       const Matrix<float, 3, 3> rgb2ycbcr{\n> +               { 0.256788235294, 0.504129411765, 0.0979058823529,\n> +                 -0.148223529412, -0.290992156863, 0.439215686275,\n> +                 0.439215686275, -0.367788235294, -0.0714274509804 }\n> +       };\n> +       const Matrix<float, 3, 3> ycbcr2rgb{\n> +               { 1.16438356164, 0, 1.59602678571,\n> +                 1.16438356164, -0.391762290094, -0.812967647235,\n> +                 1.16438356164, 2.01723214285, 0 }\n> +       };\n> +       const Matrix<float, 3, 3> saturationMatrix{\n> +               { 1, 0, 0,\n> +                 0, saturation, 0,\n> +                 0, 0, saturation }\n> +       };\n> +       ccm = ycbcr2rgb * saturationMatrix * rgb2ycbcr * ccm;\n> +}\n> +\n>  void Ccm::prepare(IPAContext &context, const uint32_t frame,\n>                   IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params)\n>  {\n> +       auto &saturation = context.activeState.knobs.saturation;\n> +\n>         const unsigned int ct = context.activeState.awb.temperatureK;\n>  \n> -       /* Change CCM only on bigger temperature changes. */\n> +       /* Change CCM only on saturation or bigger temperature changes. */\n>         if (frame > 0 &&\n> -           utils::abs_diff(ct, lastCt_) < kTemperatureThreshold) {\n> +           utils::abs_diff(ct, lastCt_) < kTemperatureThreshold &&\n> +           saturation == lastSaturation_) {\n>                 frameContext.ccm.ccm = context.activeState.ccm.ccm;\n>                 context.activeState.ccm.changed = false;\n>                 return;\n>         }\n>  \n>         lastCt_ = ct;\n> +       lastSaturation_ = saturation;\n>         Matrix<float, 3, 3> ccm = ccm_.getInterpolated(ct);\n> +       if (saturation)\n> +               updateSaturation(ccm, saturation.value());\n>  \n>         context.activeState.ccm.ccm = ccm;\n>         frameContext.ccm.ccm = ccm;\n> +       frameContext.saturation = saturation;\n>         context.activeState.ccm.changed = true;\n>  }\n>  \n> @@ -67,6 +118,9 @@ void Ccm::process([[maybe_unused]] IPAContext &context,\n>                   ControlList &metadata)\n>  {\n>         metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data());\n> +\n> +       const auto &saturation = frameContext.saturation;\n> +       metadata.set(controls::Saturation, saturation.value_or(1.0));\n>  }\n>  \n>  REGISTER_IPA_ALGORITHM(Ccm, \"Ccm\")\n> diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h\n> index f4e2b85b..2c5d2170 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>         ~Ccm() = default;\n>  \n>         int init(IPAContext &context, const YamlObject &tuningData) override;\n> +       int configure(IPAContext &context,\n> +                     const IPAConfigInfo &configInfo) override;\n> +       void queueRequest(typename Module::Context &context,\n> +                         const uint32_t frame,\n> +                         typename Module::FrameContext &frameContext,\n> +                         const ControlList &controls) override;\n>         void prepare(IPAContext &context,\n>                      const uint32_t frame,\n>                      IPAFrameContext &frameContext,\n> @@ -34,7 +42,10 @@ public:\n>                      ControlList &metadata) override;\n>  \n>  private:\n> +       void updateSaturation(Matrix<float, 3, 3> &ccm, float saturation);\n> +\n>         unsigned int lastCt_;\n> +       std::optional<float> lastSaturation_;\n>         Interpolator<Matrix<float, 3, 3>> ccm_;\n>  };\n>  \n> diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\n> index 88cc6c35..56792981 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>         struct {\n>                 /* 0..2 range, 1.0 = normal */\n>                 std::optional<double> contrast;\n> +               std::optional<double> saturation;\n\nIs there a reason why this is double while lastSaturation_ is float? The\nSaturation control is also float.\n\n>         } knobs;\n>  };\n>  \n> @@ -75,11 +76,14 @@ struct IPAFrameContext : public FrameContext {\n>                 int32_t exposure;\n>                 double gain;\n>         } sensor;\n> +\n>         struct {\n>                 double red;\n>                 double blue;\n>         } gains;\n> +\n>         std::optional<double> contrast;\n> +       std::optional<double> saturation;\n\nSame here.\n\n\nOtherwise, looks good.\n\nThanks,\n\nPaul\n\n>  };\n>  \n>  struct IPAContext {\n> -- \n> 2.49.0\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id F173FC3220\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 13 May 2025 14:36:16 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A3C0968B55;\n\tTue, 13 May 2025 16:36:16 +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 2077168B40\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 13 May 2025 16:36:15 +0200 (CEST)","from pyrite.rasen.tech (unknown\n\t[IPv6:2001:861:3a80:3300:4f2f:8c2c:b3ef:17d4])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 538A37E4;\n\tTue, 13 May 2025 16:35:59 +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=\"DKtnOIPu\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1747146959;\n\tbh=+/dxHM0fvN5nw9rKucs2i2VyIDaVRNsTxOt5ZAAucRo=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=DKtnOIPuJL7g9T3WGyXgmjcxIxN50lh/U2aCk0jg3BZeUq+ve3bam/Nvil21UlmBQ\n\tvYFXcnu9WpGdHn7E+8eirRqwpoJbWTPrV/zwOcBO5baR4FA4sldnwoK0DRuDgVHd1d\n\t0WTxVeV0cA0pcHPEOHiXU2lJpKQKPJ9czKpV/8aI=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250430112043.23807-2-mzamazal@redhat.com>","References":"<20250430112043.23807-1-mzamazal@redhat.com>\n\t<20250430112043.23807-2-mzamazal@redhat.com>","Subject":"Re: [PATCH v2 1/1] libcamera: software_isp: Add saturation control","From":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"Milan Zamazal <mzamazal@redhat.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>","To":"Milan Zamazal <mzamazal@redhat.com>, libcamera-devel@lists.libcamera.org","Date":"Tue, 13 May 2025 16:36:12 +0200","Message-ID":"<174714697240.233090.14766643513800311785@pyrite.rasen.tech>","User-Agent":"alot/0.0.0","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":34227,"web_url":"https://patchwork.libcamera.org/comment/34227/","msgid":"<85plgch8l9.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","date":"2025-05-13T14:46:26","subject":"Re: [PATCH v2 1/1] libcamera: software_isp: Add saturation control","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Hi Paul,\n\nthank you for review.\n\nPaul Elder <paul.elder@ideasonboard.com> writes:\n\n> Hi Milan,\n>\n> Thanks for the patch.\n>\n> Quoting Milan Zamazal (2025-04-30 13:20:42)\n>> Saturation control is added on top of the colour correction matrix.  A\n>> way of saturation adjustment that can be fully integrated into the\n>\n> s/way/method/ I think would be clearer (without having to reorganize the entire\n> sentence)\n>\n>> colour correction matrix is used.  The control is available only if Ccm\n>> algorithm is enabled.\n>> \n>> The control uses 0.0-2.0 value range, with 1.0 being unmodified\n>> saturation, 0.0 full desaturation and 2.0 quite saturated.\n>> \n>> The saturation is adjusted by converting to Y'CbCr colour space,\n>> applying the saturation value on the colour axes, and converting back to\n>> RGB.  ITU-R BT.601 conversion is used to convert between the colour\n>> spaces, for no particular reason.\n>> \n>> The colour correction matrix is applied before gamma and the given\n>> matrix is suitable for such a case.  Alternatively, the transformation\n>> used in libcamera rpi ccm.cpp could be used.\n>> \n>> Signed-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(-)\n>> \n>> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp\n>> index d5ba928d..021aefc9 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>>         }\n>>  \n>>         context.ccmEnabled = true;\n>> +       context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);\n>> +\n>> +       return 0;\n>> +}\n>> +\n>> +int Ccm::configure(IPAContext &context,\n>> +                  [[maybe_unused]] const IPAConfigInfo &configInfo)\n>> +{\n>> +       context.activeState.knobs.saturation = std::optional<double>();\n>>  \n>>         return 0;\n>>  }\n>>  \n>> +void Ccm::queueRequest(typename Module::Context &context,\n>> +                      [[maybe_unused]] const uint32_t frame,\n>> +                      [[maybe_unused]] typename Module::FrameContext &frameContext,\n>> +                      const ControlList &controls)\n>> +{\n>> +       const auto &saturation = controls.get(controls::Saturation);\n>> +       if (saturation.has_value()) {\n>> +               context.activeState.knobs.saturation = saturation;\n>> +               LOG(IPASoftCcm, Debug) << \"Setting saturation to \" << saturation.value();\n>> +       }\n>> +}\n>> +\n>> +void Ccm::updateSaturation(Matrix<float, 3, 3> &ccm, float saturation)\n>\n> I think applySaturation might be a more appropriate name. updateSaturation\n> sounds like saturation is the output value and it gets updated based on some\n> input.\n>\n>> +{\n>> +       /* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */\n>> +       const Matrix<float, 3, 3> rgb2ycbcr{\n>> +               { 0.256788235294, 0.504129411765, 0.0979058823529,\n>> +                 -0.148223529412, -0.290992156863, 0.439215686275,\n>> +                 0.439215686275, -0.367788235294, -0.0714274509804 }\n>> +       };\n>> +       const Matrix<float, 3, 3> ycbcr2rgb{\n>> +               { 1.16438356164, 0, 1.59602678571,\n>> +                 1.16438356164, -0.391762290094, -0.812967647235,\n>> +                 1.16438356164, 2.01723214285, 0 }\n>> +       };\n>> +       const Matrix<float, 3, 3> saturationMatrix{\n>> +               { 1, 0, 0,\n>> +                 0, saturation, 0,\n>> +                 0, 0, saturation }\n>> +       };\n>> +       ccm = ycbcr2rgb * saturationMatrix * rgb2ycbcr * ccm;\n>> +}\n>> +\n>>  void Ccm::prepare(IPAContext &context, const uint32_t frame,\n>>                   IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params)\n>>  {\n>> +       auto &saturation = context.activeState.knobs.saturation;\n>> +\n>>         const unsigned int ct = context.activeState.awb.temperatureK;\n>>  \n>> -       /* Change CCM only on bigger temperature changes. */\n>> +       /* Change CCM only on saturation or bigger temperature changes. */\n>>         if (frame > 0 &&\n>> -           utils::abs_diff(ct, lastCt_) < kTemperatureThreshold) {\n>> +           utils::abs_diff(ct, lastCt_) < kTemperatureThreshold &&\n>> +           saturation == lastSaturation_) {\n>>                 frameContext.ccm.ccm = context.activeState.ccm.ccm;\n>>                 context.activeState.ccm.changed = false;\n>>                 return;\n>>         }\n>>  \n>>         lastCt_ = ct;\n>> +       lastSaturation_ = saturation;\n>>         Matrix<float, 3, 3> ccm = ccm_.getInterpolated(ct);\n>> +       if (saturation)\n>> +               updateSaturation(ccm, saturation.value());\n>>  \n>>         context.activeState.ccm.ccm = ccm;\n>>         frameContext.ccm.ccm = ccm;\n>> +       frameContext.saturation = saturation;\n>>         context.activeState.ccm.changed = true;\n>>  }\n>>  \n>> @@ -67,6 +118,9 @@ void Ccm::process([[maybe_unused]] IPAContext &context,\n>>                   ControlList &metadata)\n>>  {\n>>         metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.ccm.data());\n>> +\n>> +       const auto &saturation = frameContext.saturation;\n>> +       metadata.set(controls::Saturation, saturation.value_or(1.0));\n>>  }\n>>  \n>>  REGISTER_IPA_ALGORITHM(Ccm, \"Ccm\")\n>> diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h\n>> index f4e2b85b..2c5d2170 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>>         ~Ccm() = default;\n>>  \n>>         int init(IPAContext &context, const YamlObject &tuningData) override;\n>> +       int configure(IPAContext &context,\n>> +                     const IPAConfigInfo &configInfo) override;\n>> +       void queueRequest(typename Module::Context &context,\n>> +                         const uint32_t frame,\n>> +                         typename Module::FrameContext &frameContext,\n>> +                         const ControlList &controls) override;\n>>         void prepare(IPAContext &context,\n>>                      const uint32_t frame,\n>>                      IPAFrameContext &frameContext,\n>> @@ -34,7 +42,10 @@ public:\n>>                      ControlList &metadata) override;\n>>  \n>>  private:\n>> +       void updateSaturation(Matrix<float, 3, 3> &ccm, float saturation);\n>> +\n>>         unsigned int lastCt_;\n>> +       std::optional<float> lastSaturation_;\n>>         Interpolator<Matrix<float, 3, 3>> ccm_;\n>>  };\n>>  \n>> diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\n>> index 88cc6c35..56792981 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>>         struct {\n>>                 /* 0..2 range, 1.0 = normal */\n>>                 std::optional<double> contrast;\n>> +               std::optional<double> saturation;\n>\n> Is there a reason why this is double while lastSaturation_ is float? The\n> Saturation control is also float.\n\nI think it was just a mistake.  I'll fix this and apply the other\nimprovements you suggested.\n\n>>         } knobs;\n>>  };\n>>  \n>> @@ -75,11 +76,14 @@ struct IPAFrameContext : public FrameContext {\n>>                 int32_t exposure;\n>>                 double gain;\n>>         } sensor;\n>> +\n>>         struct {\n>>                 double red;\n>>                 double blue;\n>>         } gains;\n>> +\n>>         std::optional<double> contrast;\n>> +       std::optional<double> saturation;\n>\n> Same here.\n>\n>\n> Otherwise, looks good.\n>\n> Thanks,\n>\n> Paul\n>\n>>  };\n>>  \n>>  struct IPAContext {\n>> -- \n>> 2.49.0\n>>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id CBEC8C3200\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 13 May 2025 14:46:34 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0067368B61;\n\tTue, 13 May 2025 16:46:33 +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 7388968B40\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 13 May 2025 16:46:32 +0200 (CEST)","from mail-wr1-f72.google.com (mail-wr1-f72.google.com\n\t[209.85.221.72]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-614-6Aj7nlacMISVsZknStf6Jw-1; Tue, 13 May 2025 10:46:30 -0400","by mail-wr1-f72.google.com with SMTP id\n\tffacd0b85a97d-3a0b662be0cso3750397f8f.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 13 May 2025 07:46:29 -0700 (PDT)","from mzamazal-thinkpadp1gen7.tpbc.csb ([85.93.96.130])\n\tby smtp.gmail.com with ESMTPSA id\n\tffacd0b85a97d-3a1f5a2d2f4sm16429823f8f.78.2025.05.13.07.46.27\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 13 May 2025 07:46:27 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"FaZe734O\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1747147591;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=dqc2MotEmoOnB0j0iDQshf1AIEnGtmPc4RP6cZ6+4mg=;\n\tb=FaZe734Ob8Ix9JH7TRVse1iVaaDZ45rDengccoMGSIyYFEL+cZ5kJfUdGBbGopy0NWprjb\n\t6VsIZmAnyqwKyyz3w191ixIYSH2FNH4Zk2gm1XCsgj3i34zn/AOifm6RpaTUUrSiIY+sqf\n\t+O4mFs6mc73Z7meXoa/22zInOVC9T90=","X-MC-Unique":"6Aj7nlacMISVsZknStf6Jw-1","X-Mimecast-MFC-AGG-ID":"6Aj7nlacMISVsZknStf6Jw_1747147589","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1747147588; x=1747752388;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=dqc2MotEmoOnB0j0iDQshf1AIEnGtmPc4RP6cZ6+4mg=;\n\tb=tpXBP4MZyXNr/68KKV4XmvuYkI6NOul9n16jivFNNKNCib65ei5Aj/FEcwrbIx8/Nv\n\tlI7Fwt73QD9evHAdvedI/M6UKoC8GzI9Fz6uZg8+j/9BZNSHlGTkGzd0+UiSY0O05rhI\n\tuA10vYi8hpyMJLV3FOVHGQGESaEOoihMufqEINO9e+28r+WFmPSORz01JckRrK/ARSHB\n\tBmPzoMtP60ozZ7521LbNQHmE398+r75d7TMLAeQe5FZp99GO0myp2VuGl7EswE8az5GL\n\t6DeRajcPAOSsZqPrK/jxu9Q0Fk2OWidUaPon35PNfqHxUVMzgPvlU2E1gd4Mzte6+vUL\n\tj3Nw==","X-Gm-Message-State":"AOJu0YzWw1zbGdVBC1fKy+Af63m50E4c2SgOk5b95lGxB4HBwcCz6k3Q\n\tF/8pV98Ac0GK5InHEy7IHX+etIx6SUQoDpJxt9oqe3DNI8aLsBqnl5An6UZv9K5oOUu7A33QMrT\n\t5v7o89tYSfLed0hSszW7DdIL8+St04iFVK3r/dXzCzzEttr7yI2mu3m33mrx0jABZsj+iE5D0Ff\n\t5yQUU=","X-Gm-Gg":"ASbGnctvrcdRcSPvq31z7fHvOC2tAPME9rSn3WXSJRAseO7D3ypeo1070xCEroglxqc\n\tusihqa1fZX3FFsMLwkIDWFe5zb1k7fy0QmTxbRti1Gle+Yp3n6GaZKl3en4n9qMZs6pxin07iQw\n\tjwDahniZt2L1hIcYKxbMwDzxgn2TF1DZ4kY1rKys7zS7VVNitRzb1LhOFAq2oJ8AZTqjrhI9JH6\n\txXca4E6XbkMnye/We3fzlMIhDpE7aO+n0JWDT6ZfIit4AvBp8V1j5g9TQraqOPWnIebhIC+MhYa\n\tybgCVfvxMr3/e1HV6CpS1+kamTYtroMjThf6","X-Received":["by 2002:a05:6000:400b:b0:3a0:a238:a59a with SMTP id\n\tffacd0b85a97d-3a1f64240c0mr13829212f8f.3.1747147588247; \n\tTue, 13 May 2025 07:46:28 -0700 (PDT)","by 2002:a05:6000:400b:b0:3a0:a238:a59a with SMTP id\n\tffacd0b85a97d-3a1f64240c0mr13829190f8f.3.1747147587783; \n\tTue, 13 May 2025 07:46:27 -0700 (PDT)"],"X-Google-Smtp-Source":"AGHT+IH/8j+sxtI1ZvN7ue1JSCehnmVYr2INmcFoS1vcNssCk4fj2RUHrLxbRnHay0gWpuSmpwLM7g==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org,  Laurent Pinchart\n\t<laurent.pinchart@ideasonboard.com>,  Kieran Bingham\n\t<kieran.bingham@ideasonboard.com>","Subject":"Re: [PATCH v2 1/1] libcamera: software_isp: Add saturation control","In-Reply-To":"<174714697240.233090.14766643513800311785@pyrite.rasen.tech>\n\t(Paul Elder's message of \"Tue, 13 May 2025 16:36:12 +0200\")","References":"<20250430112043.23807-1-mzamazal@redhat.com>\n\t<20250430112043.23807-2-mzamazal@redhat.com>\n\t<174714697240.233090.14766643513800311785@pyrite.rasen.tech>","Date":"Tue, 13 May 2025 16:46:26 +0200","Message-ID":"<85plgch8l9.fsf@mzamazal-thinkpadp1gen7.tpbc.csb>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"R_dwPaasKG4GZopoEcreEQgJ0rTZgDLnNiuGkyQbmCw_1747147589","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]