From patchwork Sun Oct 26 23:30:38 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 24824 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id D6068BE080 for ; Sun, 26 Oct 2025 23:30:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5AEE4606F8; Mon, 27 Oct 2025 00:30:57 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gN822ZId"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id EAE8D606E0 for ; Mon, 27 Oct 2025 00:30:53 +0100 (CET) Received: from charm.hippo-penny.ts.net (unknown [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 38CE4E77; Mon, 27 Oct 2025 00:29:06 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761521346; bh=1FcaQEB87OPnpDmcTCZcyq3suw1t0sx0KOfWaRXpDsw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gN822ZId2NSBWy5bEOP1F2N8efdTHaLGqDTpOEWZgz8N4Ho8tiyNWocAPGAt7XYK/ CS0NqUlVKFifCNglucxdUQAnlX7PjM6qAnxmRQYJ1kIREiXzq64gRe7iVwFdmZN08t k+dAte/yEeZng0x3JNpFFIaDL9XzP4wLlZuw+iZg= From: Kieran Bingham To: libcamera devel Cc: Kieran Bingham Subject: [PATCH 1/6] libipa: Provide a Quantized type and Quantizer support Date: Sun, 26 Oct 2025 23:30:38 +0000 Message-ID: <20251026233048.175689-2-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> References: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Frequently when handling data in IPA components we must convert and store user interface values which may be floating point values, and perform a specific operation or conversion to quantize this to a hardware value. This value may be to a fixed point type, or more custom code mappings, but in either case it is important to contain both the required hardware value, with it's effective quantized value. Provide a new storage type 'Quantized' which can be read publicly but must only be written through a controller class known as a Quantizer. Quantizers can be customised and specified by the owner of the data object and must implement conversion between floats and the underlying hardware type. Signed-off-by: Kieran Bingham --- src/ipa/libipa/meson.build | 2 + src/ipa/libipa/quantized.cpp | 167 +++++++++++++++++++++++++++++++++++ src/ipa/libipa/quantized.h | 90 +++++++++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 src/ipa/libipa/quantized.cpp create mode 100644 src/ipa/libipa/quantized.h diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 660be94054fa..804289778f72 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -17,6 +17,7 @@ libipa_headers = files([ 'lux.h', 'module.h', 'pwl.h', + 'quantized.h', ]) libipa_sources = files([ @@ -36,6 +37,7 @@ libipa_sources = files([ 'lux.cpp', 'module.cpp', 'pwl.cpp', + 'quantized.cpp', ]) libipa_includes = include_directories('..') diff --git a/src/ipa/libipa/quantized.cpp b/src/ipa/libipa/quantized.cpp new file mode 100644 index 000000000000..0078c6f740a9 --- /dev/null +++ b/src/ipa/libipa/quantized.cpp @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board. + * + * Helper class to manage conversions between floating point types and quantized + * storage and representation of those values. + */ + +#include "quantized.h" + +/** + * \file quantized.h + * \brief Quantized storage and Quantizer representations + */ + +namespace libcamera { + +namespace ipa { + +/** + * \struct Quantized + * \brief Quantized stores both an internal and quantized representation of a value + * \tparam T The quantized type (e.g., uint16_t, int16_t). + * + * This struct provides read-only access to both representations of the value. + * It does not perform any conversions or quantization logic itself. This must + * be handled externally by a quantizer that knows the appropriate behaviour. + * + * This type can be used to store values that need to be represented in both + * fixed-point or integer (for hardware interfaces) and floating-point (for user + * facing interfaces) + * + * It is intended to be used in shared context structures where both + * hardware-level and software-level representations of a value are required. + */ + +/** + * \fn float Quantized::value() const noexcept + * \brief Get the floating-point representation of the quantized value + * + * This function returns the floating-point form that was either directly assigned + * or computed from the quantized value. + * + * \return The floating-point value + */ + +/** + * \fn T Quantized::quantized() const noexcept + * \brief Get the raw quantized representation of the value + * + * This function returns the internal quantized value (e.g. \c uint8_t or \c int16_t), + * which is typically used for hardware-level programming or serialization. + * + * \return The quantized value of type \c T + */ + +/** + * \fn std::string Quantized::toString() const + * \brief Generate a string representation of the quantized and float values + * + * Produces a human-readable string including both the quantized and floating-point + * values, typically in the format: "Q:0xXX F:float". + * + * \return A formatted string representing the internal state + */ + +/** + * \fn bool Quantized::operator==(const Quantized &other) const noexcept + * \brief Compare two quantized values for equality + * + * Two \c Quantized objects are equal if their quantized values are equal. + * + * \param other The \c Quantized to compare with + * \return \c true if equal, \c false otherwise + */ + +/** + * \fn bool Quantized::operator!=(const Quantized &other) const noexcept + * \brief Compare two quantized values for inequality + * + * Two \c Quantized objects are not equal if their quantized values differ. + * + * \param other The \c Quantized to compare with + * \return \c true if not equal, \c false otherwise + */ + +/** + * \var Quantized::quantized_ + * \brief The raw quantized value + * + * This member stores the fixed-point or integer representation of the value, typically + * used for interfacing with hardware or protocols that require compact formats. + * + * It must only be updated or accessed through a Quantizer in conjunction with + * corresponding updates to \c Quantized::value_. + */ + +/** + * \var Quantized::value_ + * \brief The floating-point representation of the quantized value + * + * This member holds the floating-point equivalent of the quantized value, usually + * for use in calculations or presentation to higher-level software components. + * + * It must only be updated or accessed through a Quantizer in conjunction with + * corresponding updates to \c Quantized::quantized_. + */ + +/** + * \class Quantizer + * \brief Interface for converting between floating-point and quantized types + * \tparam T The quantized type (e.g., uint16_t, int16_t). + * + * This abstract class defines the interface for quantizers that handle + * conversions between floating-point values and their quantized representations. + * Specific quantization handlers should implement this interface and provide + * the toFloat and fromFloat methods specifically for their types. + */ + +/** + * \typedef Quantizer::qType + * \brief The underlying quantized type used by the quantizer + * + * This alias exposes the quantized type (e.g. \c uint8_t or \c int16_t) used internally + * by the \c Quantizer, which is helpful in template and debug contexts. + */ + +/** + * \fn Quantizer::fromFloat(T val) + * \brief Convert a floating-point value to its quantized representation + * + * \param val The floating-point value + * \return The quantized equivalent + */ + +/** + * \fn Quantizer::toFloat(T val) + * \brief Convert a quantized value to its floating-point representation + * + * \param val The quantized value + * \return The floating-point equivalent + */ + +/** + * \fn Quantizer::set(float val) + * \brief Set the value of a Quantized using a floating-point input + * + * This function updates both the quantized and floating-point + * representations stored in the Quantized, storing the quantized + * version of the float input. + * + * \param val The floating-point value to set + */ + +/** + * \fn Quantizer::set(T val) + * \brief Set the value of a Quantized using a quantized input + * + * This function updates both the quantized and floating-point + * representations stored in the Quantized. + * + * \param val The quantized value to set + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/quantized.h b/src/ipa/libipa/quantized.h new file mode 100644 index 000000000000..1c1963cf0848 --- /dev/null +++ b/src/ipa/libipa/quantized.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board. + * + * Helper class to manage conversions between floating point types and quantized + * storage and representation of those values. + */ + +#pragma once + +#include +#include +#include +#include + +namespace libcamera { + +namespace ipa { + +template +class Quantizer; + +template +struct Quantized { + static_assert(std::is_arithmetic_v, "Quantized: T must be an arithmetic type"); + + float value() const noexcept { return value_; } + T quantized() const noexcept { return quantized_; } + + std::string toString() const + { + using UT = std::make_unsigned_t; + std::ostringstream oss; + + oss << "Q:0x" << std::hex << std::uppercase << std::setw(sizeof(T) * 2) + << std::setfill('0'); + + if constexpr (std::is_unsigned_v) { + oss << static_cast(quantized_); + } else { + oss << static_cast(static_cast(quantized_)); + } + + oss << " F:" << value_; + + return oss.str(); + } + + bool operator==(const Quantized &other) const noexcept + { + return quantized_ == other.quantized_ && value_ == other.value_; + } + + bool operator!=(const Quantized &other) const noexcept + { + return !(*this == other); + } + +protected: + T quantized_{}; + float value_{}; +}; + +template +class Quantizer : public Quantized +{ +public: + using qType = T; + + virtual ~Quantizer() = default; + + virtual T fromFloat(float val) const = 0; + virtual float toFloat(T val) const = 0; + + void set(float val) + { + this->quantized_ = fromFloat(val); + this->value_ = toFloat(this->quantized_); + } + + void set(T val) + { + this->quantized_ = val; + this->value_ = toFloat(val); + } +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ From patchwork Sun Oct 26 23:30:39 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 24825 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id EB980BE080 for ; Sun, 26 Oct 2025 23:31:01 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 43F4F606F5; Mon, 27 Oct 2025 00:30:58 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="rlxMtj8E"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3EDB6606E6 for ; Mon, 27 Oct 2025 00:30:54 +0100 (CET) Received: from charm.hippo-penny.ts.net (unknown [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 821B9E9B; Mon, 27 Oct 2025 00:29:06 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761521346; bh=CenjjyPxspM0Qea0KDApsn4fVOkAyGFz8PmOvbVj8Oo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rlxMtj8EstVSPg9Xwpf8lhRj7U6htgnTdEBXI+CpPgzC4Eyr2CcCvibLpzokPxamv UALgI8Ewpg54LBPu4kHX7xKmW6HOdlmWAxKW5+fWEypxP6xwpxUoP821p38BIvcrqk JwktoSgCRaaQSbf3HaN3ppz+LkE8FCuDgwMCx+MY= From: Kieran Bingham To: libcamera devel Cc: Kieran Bingham Subject: [PATCH 2/6] test: libipa: Add tests for Quantizers Date: Sun, 26 Oct 2025 23:30:39 +0000 Message-ID: <20251026233048.175689-3-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> References: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Signed-off-by: Kieran Bingham --- test/ipa/libipa/meson.build | 1 + test/ipa/libipa/quantized.cpp | 149 ++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 test/ipa/libipa/quantized.cpp diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build index 2070bed70222..c3e255871f4f 100644 --- a/test/ipa/libipa/meson.build +++ b/test/ipa/libipa/meson.build @@ -5,6 +5,7 @@ libipa_test = [ {'name': 'histogram', 'sources': ['histogram.cpp']}, {'name': 'interpolator', 'sources': ['interpolator.cpp']}, {'name': 'pwl', 'sources': ['pwl.cpp'] }, + {'name': 'quantized', 'sources': ['quantized.cpp']}, ] foreach test : libipa_test diff --git a/test/ipa/libipa/quantized.cpp b/test/ipa/libipa/quantized.cpp new file mode 100644 index 000000000000..51db62a1b88a --- /dev/null +++ b/test/ipa/libipa/quantized.cpp @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2025, Ideas on Board + * + * Dual Type and Quantizer tests + */ + +#include "../src/ipa/libipa/quantized.h" + +#include +#include +#include +#include +#include + +#include "test.h" + +using namespace std; +using namespace libcamera; +using namespace ipa; + +class BrightnessHueQuantizer : public Quantizer +{ +public: + BrightnessHueQuantizer(float val) { set(val); } + BrightnessHueQuantizer(int8_t val) { set(val); } + + int8_t fromFloat(float v) const override + { + int quantized = std::lround(v * 128.0f); + return static_cast(std::clamp(quantized, -128, 127)); + } + + float toFloat(int8_t v) const override + { + return static_cast(v) / 128.0f; + } +}; + +class ContrastSaturationQuantizer : public Quantizer +{ +public: + ContrastSaturationQuantizer(float val) { set(val); } + ContrastSaturationQuantizer(uint8_t val) { set(val); } + + uint8_t fromFloat(float v) const override + { + int quantized = std::lround(v * 128.0f); + return static_cast(std::clamp(quantized, 0, 255)); + } + + float toFloat(uint8_t v) const override + { + return static_cast(v) / 128.0f; + } +}; + +using BrightnessQ = BrightnessHueQuantizer; +using HueQ = BrightnessHueQuantizer; +using ContrastQ = ContrastSaturationQuantizer; +using SaturationQ = ContrastSaturationQuantizer; + +class QuantizedTest : public Test +{ +protected: + int run() + { + /* Test construction from float */ + { + BrightnessQ b(0.5f); + if (b.quantized() != 64 || std::abs(b.value() - 0.5f) > 0.01f) + return TestFail; + } + + /* Test construction from T */ + { + ContrastQ c(uint8_t(128)); + if (c.quantized() != 128 || std::abs(c.value() - 1.0f) > 0.01f) + return TestFail; + } + + /* + * Test construction from non-float/non-T type (Expected Fail) + * These should be a compile-time error if uncommented: + */ + // BrightnessQ bad1(15); // overloaded ‘BrightnessHueQuantizer(int)’ is ambiguous + // BrightnessQ bad2(0xff); // overloaded ‘BrightnessHueQuantizer(int)’ is ambiguous + // ContrastQ bad3(0x33); // overloaded ‘ContrastSaturationQuantizer(int)’ is ambiguous + // ContrastQ bad4(55U); // overloaded ‘ContrastSaturationQuantizer(unsigned int)’ is ambiguous + + /* Test equality */ + { + BrightnessQ b1(0.5f), b2((int8_t)64); + if (!(b1 == b2)) + return TestFail; + } + + /* Test inequality */ + { + BrightnessQ b1(0.5f), b2(-0.5f); + if (b1 == b2) + return TestFail; + } + + /* Test copying */ + { + BrightnessQ b1(0.25f); + BrightnessQ b2 = b1; + if (!(b1 == b2)) + return TestFail; + } + + /* Test moving */ + { + BrightnessQ b1(0.25f); + BrightnessQ b2 = std::move(b1); // Allow move semantics + if (b2.value() != 0.25f) + return TestFail; + } + + /* Test assignment */ + { + ContrastQ c1(1.5f); + ContrastQ c2(0.0f); + c2 = c1; + if (!(c1 == c2)) + return TestFail; + } + + /* Test assignment / storage in generic type */ + { + ContrastQ c(1.5f); + Quantized q; + + q = c; + if (q.value() != c.value()) + return TestFail; + + if (q.quantized() != c.quantized()) + return TestFail; + } + + std::cout << "Quantised tests passed successfully." << std::endl; + + return TestPass; + } +}; + +TEST_REGISTER(QuantizedTest) From patchwork Sun Oct 26 23:30:40 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 24826 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 513B5C32CE for ; Sun, 26 Oct 2025 23:31:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DF6AC60758; Mon, 27 Oct 2025 00:30:58 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="r/Jpw4Q2"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4A5E5606CE for ; Mon, 27 Oct 2025 00:30:54 +0100 (CET) Received: from charm.hippo-penny.ts.net (unknown [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C644514A6; Mon, 27 Oct 2025 00:29:06 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761521346; bh=bycs83QWG4GQExYg5Tz8fnZpbW85jOhKNNeo9GT5w9Q=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=r/Jpw4Q2LLOvrKI1kVqUcwAsLrIuGGAiNvbSimyjrf9aF8HnHTNqrm33pJKhV8KV2 IYWHSuEu9+Me0CsR9GkjOP2Ou1qwEdFFnnpkqz0GIDiWRKK/1dzU7JN9Urwls2C/7E 2hwIT+vDDuudivEcMydRbRz9KtOO3XsmqOJEY9xw= From: Kieran Bingham To: libcamera devel Cc: Kieran Bingham Subject: [PATCH 3/6] ipa: rkisp1: cproc: Convert to use Quantized types Date: Sun, 26 Oct 2025 23:30:40 +0000 Message-ID: <20251026233048.175689-4-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> References: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Convert the Contrast and Saturuation helper functions to a Quantizer type to support maintaining the data. Signed-off-by: Kieran Bingham --- src/ipa/rkisp1/algorithms/cproc.cpp | 67 +++++++++++++++++++++-------- src/ipa/rkisp1/ipa_context.h | 13 +++--- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp index d1fff6990d37..44c0fb8425de 100644 --- a/src/ipa/rkisp1/algorithms/cproc.cpp +++ b/src/ipa/rkisp1/algorithms/cproc.cpp @@ -14,6 +14,8 @@ #include +#include "libipa/quantized.h" + /** * \file cproc.h */ @@ -39,15 +41,44 @@ constexpr float kDefaultBrightness = 0.0f; constexpr float kDefaultContrast = 1.0f; constexpr float kDefaultSaturation = 1.0f; -int convertBrightness(const float v) +class BrightnessQ : public Quantizer { - return std::clamp(std::lround(v * 128), -128, 127); -} +public: + BrightnessQ(const float val) { set(val); } + BrightnessQ(const int8_t val) { set(val); } -int convertContrastOrSaturation(const float v) + int8_t fromFloat(float v) const override + { + int quantized = std::lround(v * 128.0f); + return static_cast(std::clamp(quantized, -128, 127)); + } + + float toFloat(int8_t v) const override + { + return static_cast(v) / 128.0f; + } +}; + +class ContrastSaturationQuantizer : public Quantizer { - return std::clamp(std::lround(v * 128), 0, 255); -} +public: + ContrastSaturationQuantizer(const float val) { set(val); } + ContrastSaturationQuantizer(const uint8_t val) { set(val); } + + uint8_t fromFloat(const float v) const override + { + int quantized = std::lround(v * 128.0f); + return static_cast(std::clamp(quantized, 0, 255)); + } + + float toFloat(const uint8_t v) const override + { + return static_cast(v) / 128.0f; + } +}; + +using ContrastQ = ContrastSaturationQuantizer; +using SaturationQ = ContrastSaturationQuantizer; } /* namespace */ @@ -74,9 +105,9 @@ int ColorProcessing::configure(IPAContext &context, { auto &cproc = context.activeState.cproc; - cproc.brightness = convertBrightness(kDefaultBrightness); - cproc.contrast = convertContrastOrSaturation(kDefaultContrast); - cproc.saturation = convertContrastOrSaturation(kDefaultSaturation); + cproc.brightness = BrightnessQ(kDefaultBrightness); + cproc.contrast = ContrastQ(kDefaultContrast); + cproc.saturation = SaturationQ(kDefaultSaturation); return 0; } @@ -97,35 +128,35 @@ void ColorProcessing::queueRequest(IPAContext &context, const auto &brightness = controls.get(controls::Brightness); if (brightness) { - int value = convertBrightness(*brightness); + BrightnessQ value = *brightness; if (cproc.brightness != value) { cproc.brightness = value; update = true; } - LOG(RkISP1CProc, Debug) << "Set brightness to " << value; + LOG(RkISP1CProc, Debug) << "Set brightness to " << value.value(); } const auto &contrast = controls.get(controls::Contrast); if (contrast) { - int value = convertContrastOrSaturation(*contrast); + ContrastQ value = *contrast; if (cproc.contrast != value) { cproc.contrast = value; update = true; } - LOG(RkISP1CProc, Debug) << "Set contrast to " << value; + LOG(RkISP1CProc, Debug) << "Set contrast to " << value.value(); } const auto saturation = controls.get(controls::Saturation); if (saturation) { - int value = convertContrastOrSaturation(*saturation); + SaturationQ value = *saturation; if (cproc.saturation != value) { cproc.saturation = value; update = true; } - LOG(RkISP1CProc, Debug) << "Set saturation to " << value; + LOG(RkISP1CProc, Debug) << "Set saturation to " << value.value(); } frameContext.cproc.brightness = cproc.brightness; @@ -148,9 +179,9 @@ void ColorProcessing::prepare([[maybe_unused]] IPAContext &context, auto config = params->block(); config.setEnabled(true); - config->brightness = frameContext.cproc.brightness; - config->contrast = frameContext.cproc.contrast; - config->sat = frameContext.cproc.saturation; + config->brightness = frameContext.cproc.brightness.quantized(); + config->contrast = frameContext.cproc.contrast.quantized(); + config->sat = frameContext.cproc.saturation.quantized(); } REGISTER_IPA_ALGORITHM(ColorProcessing, "ColorProcessing") diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index f85a130d9c23..b7cdfc36b275 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -27,6 +27,7 @@ #include #include #include "libipa/agc_mean_luminance.h" +#include "libipa/quantized.h" namespace libcamera { @@ -115,9 +116,9 @@ struct IPAActiveState { } ccm; struct { - int8_t brightness; - uint8_t contrast; - uint8_t saturation; + Quantized brightness; + Quantized contrast; + Quantized saturation; } cproc; struct { @@ -169,9 +170,9 @@ struct IPAFrameContext : public FrameContext { } awb; struct { - int8_t brightness; - uint8_t contrast; - uint8_t saturation; + Quantized brightness; + Quantized contrast; + Quantized saturation; bool update; } cproc; From patchwork Sun Oct 26 23:30:41 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 24827 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id BB61FBE080 for ; Sun, 26 Oct 2025 23:31:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 352AC60756; Mon, 27 Oct 2025 00:31:00 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YbdaoOOG"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7F86F606E9 for ; Mon, 27 Oct 2025 00:30:54 +0100 (CET) Received: from charm.hippo-penny.ts.net (unknown [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1A0AB1661; Mon, 27 Oct 2025 00:29:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761521347; bh=RuIDESQUBiZB1UO6fuAvGg5sdBF5b7pkHvg/3Mbpqig=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YbdaoOOGrVBn2GJEgYWZsVKWVoEF2dkY1ki5e8j5RwyD0L4zhalk5yUhiflZegBAS wDK+s1tzqXtF/1PVBN3Qfv8ntBfZpse+f4ERzSE09ugF9E0SAlugWgPkZwQKVWBr+i fAyx1Et9LcRom1RtaTVvTOe5jecVBBTIlgiuRNFk= From: Kieran Bingham To: libcamera devel Cc: Kieran Bingham Subject: [PATCH 4/6] ipa: rkisp1: cproc: Report metadata Date: Sun, 26 Oct 2025 23:30:41 +0000 Message-ID: <20251026233048.175689-5-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> References: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Presently the colour processing component exposes controls for brightness, saturation, and contrast to the applications which are handled and processed accordingly on the ISP. The implementation lacks reporting the values that are set back to the application. Utilise the new Quantised types to provide the values that were applied to the hardware and report them in the completed request metadata. Signed-off-by: Kieran Bingham --- src/ipa/rkisp1/algorithms/cproc.cpp | 12 ++++++++++++ src/ipa/rkisp1/algorithms/cproc.h | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp index 44c0fb8425de..d19d808cce4d 100644 --- a/src/ipa/rkisp1/algorithms/cproc.cpp +++ b/src/ipa/rkisp1/algorithms/cproc.cpp @@ -184,6 +184,18 @@ void ColorProcessing::prepare([[maybe_unused]] IPAContext &context, config->sat = frameContext.cproc.saturation.quantized(); } +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void ColorProcessing::process([[maybe_unused]] IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, [[maybe_unused]] const rkisp1_stat_buffer *stats, + ControlList &metadata) +{ + metadata.set(controls::Brightness, frameContext.cproc.brightness.value()); + metadata.set(controls::Contrast, frameContext.cproc.contrast.value()); + metadata.set(controls::Saturation, frameContext.cproc.saturation.value()); +} + REGISTER_IPA_ALGORITHM(ColorProcessing, "ColorProcessing") } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/cproc.h b/src/ipa/rkisp1/algorithms/cproc.h index fd38fd17e8bb..9b589ebd4ad7 100644 --- a/src/ipa/rkisp1/algorithms/cproc.h +++ b/src/ipa/rkisp1/algorithms/cproc.h @@ -30,6 +30,10 @@ public: void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp1_stat_buffer *stats, + ControlList &metadata) override; }; } /* namespace ipa::rkisp1::algorithms */ From patchwork Sun Oct 26 23:30:42 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 24828 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id BA58FC32CE for ; Sun, 26 Oct 2025 23:31:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B80836075B; Mon, 27 Oct 2025 00:31:01 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cLHusOEp"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D6DA6606F4 for ; Mon, 27 Oct 2025 00:30:54 +0100 (CET) Received: from charm.hippo-penny.ts.net (unknown [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 61B7816AE; Mon, 27 Oct 2025 00:29:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761521347; bh=ZKkzWK8MyPkrNpdZamvE/l+3pW8Upx3O5+XyrBpeLiw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cLHusOEpkACgpkA+++yWIsuazQpQxO1+TTVm8P2dDgmflhmH8hxx/faN105r93Hv7 IQTlPiAKUXCfxF2qMSapiBDSc+cc/NgMcfw4XLrQu+vSC4xAFikVty4su6spnAYlQc 1jUf7wYeHymT4QPquNvE1XdJ4fTDJsZztYHskfKQ= From: Kieran Bingham To: libcamera devel Cc: "van Veen, Stephan" , Kieran Bingham Subject: [PATCH 5/6] libcamera: controls: Define a new core Hue control Date: Sun, 26 Oct 2025 23:30:42 +0000 Message-ID: <20251026233048.175689-6-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> References: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: "van Veen, Stephan" Define a new control to support configuration of Hue adjustments when supported by the available platform. Signed-off-by: van Veen, Stephan [Kieran: Rework to define as a rotation in degrees] Signed-off-by: Kieran Bingham --- src/libcamera/control_ids_core.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libcamera/control_ids_core.yaml b/src/libcamera/control_ids_core.yaml index f781865859ac..4993da14b3f6 100644 --- a/src/libcamera/control_ids_core.yaml +++ b/src/libcamera/control_ids_core.yaml @@ -1346,4 +1346,17 @@ controls: reduces the WdrExposureValue until the amount of pixels that are close to saturation is lower than this value. + - Hue: + type: float + direction: inout + description: | + Adjusts the image hue (colour rotation) in degrees. + + The value specifies a rotation around the colour wheel: + positive values rotate hues counter-clockwise (for example a +60 degree + rotation turns Red hues to Magenta hues), and negative values rotate + clockwise (a -60 degree rotation turns Red hues to Yellow hues). + + A value of 0 leaves the hue unchanged. + ... From patchwork Sun Oct 26 23:30:43 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 24829 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 720A2BE080 for ; Sun, 26 Oct 2025 23:31:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CD9C160751; Mon, 27 Oct 2025 00:31:03 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="HbQxr1i3"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2BCE1606F8 for ; Mon, 27 Oct 2025 00:30:55 +0100 (CET) Received: from charm.hippo-penny.ts.net (unknown [209.38.108.23]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B8A69177B; Mon, 27 Oct 2025 00:29:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761521347; bh=KP+YWnztm0c8cLSMzDHVFJJYgv58QJA33dIgfbcoYJI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HbQxr1i34M5giUy2XfueB8dZJvHQHpkeoHEwLQyH0Z222fUKQGxrS5pKSgoTOG1YV 7h+QPWLWUbVXJNvj3uc81F/3XOO3UU6RXnqnOTSOeyNokU53zceJzMmzDKATwct1qD VpSYiFnqVI6KQCxsvJH4436i2ISAKO1atdKQBuMw= From: Kieran Bingham To: libcamera devel Cc: Kieran Bingham Subject: [PATCH 6/6] ipa: rkisp1: cproc: Provide a Hue control Date: Sun, 26 Oct 2025 23:30:43 +0000 Message-ID: <20251026233048.175689-7-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> References: <20251026233048.175689-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The RKISP1 supports a configurable Hue as part of the colour processing unit (cproc). This is implemented as a phase shift of the chrominance values between -90 and +87.188 degrees. Implement the new control converting to the hardware scale accordingly and report the applied control in the completed request metadata. Signed-off-by: Kieran Bingham --- src/ipa/rkisp1/algorithms/cproc.cpp | 38 +++++++++++++++++++++++++++++ src/ipa/rkisp1/ipa_context.h | 2 ++ 2 files changed, 40 insertions(+) diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp index d19d808cce4d..a92bf05131ba 100644 --- a/src/ipa/rkisp1/algorithms/cproc.cpp +++ b/src/ipa/rkisp1/algorithms/cproc.cpp @@ -39,6 +39,7 @@ namespace { constexpr float kDefaultBrightness = 0.0f; constexpr float kDefaultContrast = 1.0f; +constexpr float kDefaultHue = 0.0f; constexpr float kDefaultSaturation = 1.0f; class BrightnessQ : public Quantizer @@ -59,6 +60,27 @@ public: } }; +class HueQ : public Quantizer +{ +public: + HueQ(float val) { set(val); } + HueQ(int8_t val) { set(val); } + + static constexpr float MaxDegrees = 90; + static constexpr float q = 128.0f / MaxDegrees; + + int8_t fromFloat(float v) const override + { + int quantized = std::lround(v * q); + return static_cast(std::clamp(quantized, -128, 127)); + } + + float toFloat(int8_t v) const override + { + return static_cast(v) / q; + } +}; + class ContrastSaturationQuantizer : public Quantizer { public: @@ -92,6 +114,7 @@ int ColorProcessing::init(IPAContext &context, cmap[&controls::Brightness] = ControlInfo(-1.0f, 0.993f, kDefaultBrightness); cmap[&controls::Contrast] = ControlInfo(0.0f, 1.993f, kDefaultContrast); + cmap[&controls::Hue] = ControlInfo(-90.0f, 90.0f, kDefaultHue); cmap[&controls::Saturation] = ControlInfo(0.0f, 1.993f, kDefaultSaturation); return 0; @@ -107,6 +130,7 @@ int ColorProcessing::configure(IPAContext &context, cproc.brightness = BrightnessQ(kDefaultBrightness); cproc.contrast = ContrastQ(kDefaultContrast); + cproc.hue = HueQ(kDefaultHue); cproc.saturation = SaturationQ(kDefaultSaturation); return 0; @@ -148,6 +172,17 @@ void ColorProcessing::queueRequest(IPAContext &context, LOG(RkISP1CProc, Debug) << "Set contrast to " << value.value(); } + const auto &hue = controls.get(controls::Hue); + if (hue) { + HueQ value = *hue; + if (cproc.hue != value) { + cproc.hue = value; + update = true; + } + + LOG(RkISP1CProc, Debug) << "Set hue to " << value.value(); + } + const auto saturation = controls.get(controls::Saturation); if (saturation) { SaturationQ value = *saturation; @@ -161,6 +196,7 @@ void ColorProcessing::queueRequest(IPAContext &context, frameContext.cproc.brightness = cproc.brightness; frameContext.cproc.contrast = cproc.contrast; + frameContext.cproc.hue = cproc.hue; frameContext.cproc.saturation = cproc.saturation; frameContext.cproc.update = update; } @@ -181,6 +217,7 @@ void ColorProcessing::prepare([[maybe_unused]] IPAContext &context, config.setEnabled(true); config->brightness = frameContext.cproc.brightness.quantized(); config->contrast = frameContext.cproc.contrast.quantized(); + config->hue = frameContext.cproc.hue.quantized(); config->sat = frameContext.cproc.saturation.quantized(); } @@ -193,6 +230,7 @@ void ColorProcessing::process([[maybe_unused]] IPAContext &context, [[maybe_unus { metadata.set(controls::Brightness, frameContext.cproc.brightness.value()); metadata.set(controls::Contrast, frameContext.cproc.contrast.value()); + metadata.set(controls::Hue, frameContext.cproc.hue.value()); metadata.set(controls::Saturation, frameContext.cproc.saturation.value()); } diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index b7cdfc36b275..51281e7f7b82 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -118,6 +118,7 @@ struct IPAActiveState { struct { Quantized brightness; Quantized contrast; + Quantized hue; Quantized saturation; } cproc; @@ -172,6 +173,7 @@ struct IPAFrameContext : public FrameContext { struct { Quantized brightness; Quantized contrast; + Quantized hue; Quantized saturation; bool update; } cproc;