From patchwork Fri Sep 18 12:09:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 28356 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 271C5C3364 for ; Fri, 18 Sep 2026 12:10:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BA3EF68828; Fri, 18 Sep 2026 14:10:19 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KszNdqHs"; 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 273D46873A for ; Fri, 18 Sep 2026 14:09:58 +0200 (CEST) Received: from pb-laptop.local (185.221.142.0.nat.pool.zt.hu [185.221.142.0]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4A7A14C39; Fri, 18 Sep 2026 14:08:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1789733295; bh=IoL/kQ6tf7IHi+nJgJLBGFM6cs7UL5DEq8qSKY6s8u4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KszNdqHsrQtDtFKzxO+ZW+z8GLd1/FvA7XbEXp1GIeLaenbLAvrgZs6ueI6L3UKwx lmf5ZMMUvYsO8uu/OkOgpZwbEtI8VX2IKT5Vmr3bNgw+hNCJydtyrNB70exOMc8Jor LrMYYlGt5nxqRX1uEaMxslxsnb4DLL1W++EQ2AFM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v3 19/21] ipa: rppx1: gsl: Add Date: Fri, 18 Sep 2026 14:09:47 +0200 Message-ID: <20260918120949.191668-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260918120949.191668-1-barnabas.pocze@ideasonboard.com> References: <20260918120949.191668-1-barnabas.pocze@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: Jacopo Mondi Add the algorithm to the rppx1 ipa module based on the corresponding algorithm in the rkisp1 ipa module. Signed-off-by: Jacopo Mondi --- src/ipa/rppx1/algorithms/gsl.cpp | 151 +++++++++++++++++++++++++++ src/ipa/rppx1/algorithms/gsl.h | 35 +++++++ src/ipa/rppx1/algorithms/meson.build | 1 + src/ipa/rppx1/params.h | 4 + 4 files changed, 191 insertions(+) create mode 100644 src/ipa/rppx1/algorithms/gsl.cpp create mode 100644 src/ipa/rppx1/algorithms/gsl.h diff --git a/src/ipa/rppx1/algorithms/gsl.cpp b/src/ipa/rppx1/algorithms/gsl.cpp new file mode 100644 index 0000000000..1f2d52d338 --- /dev/null +++ b/src/ipa/rppx1/algorithms/gsl.cpp @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Gamma Sensor Linearization control + */ + +#include "gsl.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "libcamera/internal/value_node.h" + +/** + * \file gsl.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +LOG_DEFINE_CATEGORY(RppX1Gsl) + +/* `LinPre1` only */ +constexpr uint32_t kDxOffset = 8; +constexpr uint32_t kMaxDx = (1u << 4) - 1; +constexpr uint32_t kResolution = 1u << 24; + +namespace { + +[[nodiscard]] +bool +parseYLut(const ValueNode &yObject, std::string_view key, uint32_t scale, + std::span res) +{ + auto vals = yObject[key].get>().value_or(utils::defopt); + if (vals.size() != res.size()) { + LOG(RppX1Gsl, Error) + << "Invalid 'y:" << key << "': expected " + << res.size() << " elements, got " << vals.size(); + return false; + } + + for (const auto &[i, y] : utils::enumerate(vals)) { + if (y < 0 || y > 1) { + LOG(RppX1Gsl, Error) + << "Invalid 'y:" << key << "': elements must be in [0; 1]"; + return false; + } + + res[i] = std::lround(y * scale); + } + + return true; +} + +} /* namespace */ + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context, + const ValueNode &tuningData) +{ + /* + * \todo `LinPre2` has different ranges: + * * kDxOffset = 4 + * * kMaxDx = 7 + * * kResolution = 2^12 + * Maybe this parameter could be reworked to be compatible with both. + */ + gammaDx_ = tuningData["x-intervals"].get>().value_or(utils::defopt); + if (gammaDx_.size() != RPPX1_LIN_SAMPLE_POINTS_NUM) { + LOG(RppX1Gsl, Error) + << "Invalid 'x-intervals': expected " + << RPPX1_LIN_SAMPLE_POINTS_NUM << " elements, got " + << gammaDx_.size(); + + return -EINVAL; + } + + uint32_t xSum = 0; + for (const auto &x : gammaDx_) { + if (x > kMaxDx) { + LOG(RppX1Gsl, Error) + << "Invalid 'x-intervals': must be at most " << kMaxDx + << ", got " << x; + return -EINVAL; + } + + xSum += 1u << (x + kDxOffset); + } + + /* + * > Typically, the accumulated sum of dx[i] should cover the complete data + * > input range of [0; 2^12) or [0; 2^24). + */ + if (xSum != kResolution) { + LOG(RppX1Gsl, Error) + << "Invalid 'x-intervals': must cover full input range of " << kResolution + << ", got " << xSum; + return -EINVAL; + } + + const ValueNode &yObject = tuningData["y"]; + if (!yObject.isDictionary()) { + LOG(RppX1Gsl, Error) + << "Invalid 'y': must be a dictionary"; + return -EINVAL; + } + + if (!parseYLut(yObject, "red", kResolution - 1, curveYr_) || + !parseYLut(yObject, "green", kResolution - 1, curveYg_) || + !parseYLut(yObject, "blue", kResolution - 1, curveYb_)) + return -EINVAL; + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context, + const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + RppX1Params *params) +{ + if (frame > 0) + return; + + auto config = params->block(); + config.setEnabled(true); + + std::copy(gammaDx_.begin(), gammaDx_.end(), config->dx); + std::copy(curveYr_.begin(), curveYr_.end(), config->curve_r); + std::copy(curveYg_.begin(), curveYg_.end(), config->curve_g); + std::copy(curveYb_.begin(), curveYb_.end(), config->curve_b); +} + +REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/gsl.h b/src/ipa/rppx1/algorithms/gsl.h new file mode 100644 index 0000000000..0c86fbf1ff --- /dev/null +++ b/src/ipa/rppx1/algorithms/gsl.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Gamma Sensor Linearization control + */ + +#pragma once + +#include + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class GammaSensorLinearization : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + +private: + std::vector gammaDx_; + std::array curveYr_; + std::array curveYg_; + std::array curveYb_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index d93b22c634..e7d78a00d2 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -6,5 +6,6 @@ rppx1_ipa_algorithms = files([ 'blc.cpp', 'ccm.cpp', 'goc.cpp', + 'gsl.cpp', 'lux.cpp', ]) diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h index b724fa3162..87a70d960f 100644 --- a/src/ipa/rppx1/params.h +++ b/src/ipa/rppx1/params.h @@ -22,6 +22,8 @@ enum class BlockType : uint16_t { ExmPre1, GaHv, HistPost, + LinPre1, + LinPre2, WbMeasPost, }; @@ -45,6 +47,8 @@ RPPX1_DEFINE_BLOCK_TYPE(CcorPost, ccor, CCOR_POST) RPPX1_DEFINE_BLOCK_TYPE(ExmPre1, exm, EXM_PRE1) RPPX1_DEFINE_BLOCK_TYPE(GaHv, ga, GA_HV) RPPX1_DEFINE_BLOCK_TYPE(HistPost, hist, HIST_POST) +RPPX1_DEFINE_BLOCK_TYPE(LinPre1, lin, LIN_PRE1) +RPPX1_DEFINE_BLOCK_TYPE(LinPre2, lin, LIN_PRE2) RPPX1_DEFINE_BLOCK_TYPE(WbMeasPost, wbmeas, WBMEAS_POST) struct params_traits {