From patchwork Fri Sep 18 12:09:48 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: 28358 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 6F899C3366 for ; Fri, 18 Sep 2026 12:10:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 185E868762; Fri, 18 Sep 2026 14:10:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="VLpjllYa"; 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 6B6E768747 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 8AA3A4C2F; 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=Lz7Qk+JWALuAQ60CEa9Jb+0CHga48/PbDBzmV27RtIE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VLpjllYaUZ/qmCKBh5ZNgv20vpWClejSx//C96447S4+5+xigVjH4CsmNRcXWSzAh 7EAIjvbe4GnIGC1MhFvZ+zbMpLzoEvPdNPI+nozwv64FUGI4VE28EMMRgUq/7dNFiw vnFeAxKh90sykX7r0x4swxCZWurqJg0Q/3Hud8Mo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v3 20/21] ipa: rppx1: lsc: Add Date: Fri, 18 Sep 2026 14:09:48 +0200 Message-ID: <20260918120949.191668-21-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/lsc.cpp | 272 +++++++++++++++++++++++++++ src/ipa/rppx1/algorithms/lsc.h | 58 ++++++ src/ipa/rppx1/algorithms/meson.build | 1 + src/ipa/rppx1/ipa_context.h | 5 + src/ipa/rppx1/params.h | 4 + 5 files changed, 340 insertions(+) create mode 100644 src/ipa/rppx1/algorithms/lsc.cpp create mode 100644 src/ipa/rppx1/algorithms/lsc.h diff --git a/src/ipa/rppx1/algorithms/lsc.cpp b/src/ipa/rppx1/algorithms/lsc.cpp new file mode 100644 index 0000000000..1b0dbc792d --- /dev/null +++ b/src/ipa/rppx1/algorithms/lsc.cpp @@ -0,0 +1,272 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Lens Shading Correction control + */ + +#include "lsc.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "libcamera/internal/value_node.h" + +/** + * \file lsc.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +LOG_DEFINE_CATEGORY(RppX1Lsc) + +namespace { + +constexpr int kColourTemperatureQuantization = 10; + +std::vector parseSizes(const ValueNode &tuningData, + const char *prop) +{ + std::vector sizes = + tuningData[prop].get>().value_or(utils::defopt); + if (sizes.size() != RPPX1_LSC_NUM_SECTORS) { + LOG(RppX1Lsc, Error) + << "Invalid '" << prop << "' values: expected " + << RPPX1_LSC_NUM_SECTORS + << " elements, got " << sizes.size(); + return {}; + } + + /* + * The sum of all elements must satisfy hardware constraints. + * Validate it here, allowing a 1% tolerance as rounding errors may + * prevent an exact match (further adjustments will be performed in + * LensShadingCorrection::prepare()). + */ + constexpr double expectedSum = 1.0; + + double sum = std::accumulate(sizes.begin(), sizes.end(), 0.0); + if (std::abs(sum - expectedSum) > 0.01) { + LOG(RppX1Lsc, Error) + << "Invalid '" << prop << "' values: sum of the elements" + << " should be " << expectedSum << ", got " << sum; + return {}; + } + + const auto &[min, max] = std::minmax_element(sizes.begin(), sizes.end()); + if (*min <= 0 || *max > 1) { + LOG(RppX1Lsc, Error) + << "Invalid '" << prop << "' values: elements must be in (0;1]"; + return {}; + } + + return sizes; +} + +std::vector sizesListToPositions(std::span sizes) +{ + std::vector positions(sizes.size() + 1); + + positions[0] = 0; + for (size_t i = 0; i < sizes.size(); i++) + positions[i + 1] = positions[i] + sizes[i]; + + return positions; +} + +unsigned int quantize(unsigned int value, unsigned int step) +{ + return std::lround(value / static_cast(step)) * step; +} + +} /* namespace */ + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int LensShadingCorrection::init([[maybe_unused]] IPAContext &context, + const ValueNode &tuningData) +{ + xSize_ = parseSizes(tuningData, "x-size"); + ySize_ = parseSizes(tuningData, "y-size"); + + if (xSize_.empty() || ySize_.empty()) + return -EINVAL; + + xPos_ = sizesListToPositions(xSize_); + yPos_ = sizesListToPositions(ySize_); + + return lscAlgo_.init(tuningData, context.ctrlMap, { + .keys = { "r", "gr", "gb", "b" }, + .numHSamples = RPPX1_LSC_SAMPLES_MAX, + .numVSamples = RPPX1_LSC_SAMPLES_MAX, + .sensorSize = context.sensorInfo.activeAreaSize + }); +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int LensShadingCorrection::configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) +{ + const Size &size = configInfo.outputSize; + Size totalSize{}; + + for (unsigned int i = 0; i < RPPX1_LSC_NUM_SECTORS; ++i) { + xSizes_[i] = xSize_[i] * size.width; + ySizes_[i] = ySize_[i] * size.height; + + /* + * > The sum of all 16 {x,y}-size values must exactly match + * > image {width,height}. + * + * Enforce it by computing the last table value separately to + * avoid rounding-induced errors. + */ + if (i == RPPX1_LSC_NUM_SECTORS - 1) { + xSizes_[i] = size.width - totalSize.width; + ySizes_[i] = size.height - totalSize.height; + } + + LOG(RppX1Lsc, Debug) + << i << ": " << xSizes_[i] << "=" << size.width << '*' << xSize_[i] << ", " + << ySizes_[i] << "=" << size.height << '*' << ySize_[i]; + + /* + * > The sector size in {x,y}-direction must be greater than 7. + */ + if (xSizes_[i] < 8 || ySizes_[i] < 8) + return -EINVAL; + if (xSizes_[i] > 1023 || ySizes_[i] > 1023) + return -EINVAL; + + totalSize.width += xSizes_[i]; + totalSize.height += ySizes_[i]; + + /* + * > The gradient values are related to the sector sizes and can be + * > calculated for both x and y direction using the following equation: + * > gradient[i] = int(2^15 / size[i] + 1/2) where i in [0;15] + */ + xGrad_[i] = std::lround((1u << 15) / xSizes_[i]); + yGrad_[i] = std::lround((1u << 15) / ySizes_[i]); + } + + lastAppliedCt_ = 0; + lastAppliedQuantizedCt_ = 0; + + return lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop, + xPos_, yPos_); +} + +void LensShadingCorrection::setParameters(rppx1_lsc_params &config) +{ + static_assert(sizeof(config.x_sect_size) == sizeof(xSizes_)); + memcpy(config.x_sect_size, xSizes_, sizeof(xSizes_)); + + static_assert(sizeof(config.y_sect_size) == sizeof(ySizes_)); + memcpy(config.y_sect_size, ySizes_, sizeof(ySizes_)); + + static_assert(sizeof(config.x_grad) == sizeof(xGrad_)); + memcpy(config.x_grad, xGrad_, sizeof(xGrad_)); + + static_assert(sizeof(config.y_grad) == sizeof(yGrad_)); + memcpy(config.y_grad, yGrad_, sizeof(yGrad_)); +} + +void LensShadingCorrection::copyTable(rppx1_lsc_params &config, + const lsc::Components &set) +{ + const auto &r = set.at("r"); + std::copy(r.begin(), r.end(), &config.r_data[0][0]); + const auto &gr = set.at("gr"); + std::copy(gr.begin(), gr.end(), &config.gr_data[0][0]); + const auto &gb = set.at("gb"); + std::copy(gb.begin(), gb.end(), &config.gb_data[0][0]); + const auto &b = set.at("b"); + std::copy(b.begin(), b.end(), &config.b_data[0][0]); +} + +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void LensShadingCorrection::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, + controls); +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void LensShadingCorrection::prepare([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) +{ + uint32_t ct = frameContext.awb.colourTemperature; + unsigned int quantizedCt = quantize(ct, kColourTemperatureQuantization); + + /* Check if we can skip the update. */ + if (!frameContext.lsc.update) { + if (!frameContext.lsc.enabled) + return; + + /* + * Add a threshold so that oscillations around a quantization + * step don't lead to constant changes. + */ + if (utils::abs_diff(ct, lastAppliedCt_) < kColourTemperatureQuantization / 2) + return; + + if (quantizedCt == lastAppliedQuantizedCt_) + return; + } + + auto config = params->block(); + config.setEnabled(frameContext.lsc.enabled); + + if (!frameContext.lsc.enabled) + return; + + setParameters(*config); + + copyTable(*config, lscAlgo_.interpolateComponents(quantizedCt)); + + lastAppliedCt_ = ct; + lastAppliedQuantizedCt_ = quantizedCt; + + LOG(RppX1Lsc, Debug) + << "ct is " << ct << ", quantized to " + << quantizedCt; +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void LensShadingCorrection::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const RppX1Stats *stats, + ControlList &metadata) +{ + lscAlgo_.process(frameContext.lsc, metadata); +} + +REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/lsc.h b/src/ipa/rppx1/algorithms/lsc.h new file mode 100644 index 0000000000..c731637bee --- /dev/null +++ b/src/ipa/rppx1/algorithms/lsc.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Lens Shading Correction control + */ + +#pragma once + +#include + +#include "libipa/fixedpoint.h" +#include "libipa/lsc.h" + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class LensShadingCorrection : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const RppX1Stats *stats, + ControlList &metadata) override; +private: + void setParameters(rppx1_lsc_params &config); + void copyTable(rppx1_lsc_params &config, + const lsc::Components &set); + + std::vector xSize_; + std::vector ySize_; + std::vector xPos_; + std::vector yPos_; + uint16_t xGrad_[RPPX1_LSC_NUM_SECTORS]; + uint16_t yGrad_[RPPX1_LSC_NUM_SECTORS]; + uint16_t xSizes_[RPPX1_LSC_NUM_SECTORS]; + uint16_t ySizes_[RPPX1_LSC_NUM_SECTORS]; + + unsigned int lastAppliedCt_ = 0; + unsigned int lastAppliedQuantizedCt_ = 0; + + LscAlgorithm> lscAlgo_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index e7d78a00d2..7883880f9b 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -7,5 +7,6 @@ rppx1_ipa_algorithms = files([ 'ccm.cpp', 'goc.cpp', 'gsl.cpp', + 'lsc.cpp', 'lux.cpp', ]) diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h index 0d7b298f43..0be3cef0e4 100644 --- a/src/ipa/rppx1/ipa_context.h +++ b/src/ipa/rppx1/ipa_context.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace libcamera { @@ -49,6 +50,8 @@ struct IPAActiveState { ipa::gamma::ActiveState goc; + ipa::lsc::ActiveState lsc; + struct { double lux; } lux; @@ -71,6 +74,8 @@ struct IPAFrameContext : public FrameContext { ipa::gamma::FrameContext goc; + ipa::lsc::FrameContext lsc; + struct { double lux; } lux; diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h index 87a70d960f..0f9de424fe 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, + LscPre1, + LscPre2, LinPre1, LinPre2, WbMeasPost, @@ -47,6 +49,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(LscPre1, lsc, LSC_PRE1) +RPPX1_DEFINE_BLOCK_TYPE(LscPre2, lsc, LSC_PRE2) RPPX1_DEFINE_BLOCK_TYPE(LinPre1, lin, LIN_PRE1) RPPX1_DEFINE_BLOCK_TYPE(LinPre2, lin, LIN_PRE2) RPPX1_DEFINE_BLOCK_TYPE(WbMeasPost, wbmeas, WBMEAS_POST)