From patchwork Thu Oct 22 09:40:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Milan Zamazal X-Patchwork-Id: 28553 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 1FD76C3261 for ; Thu, 22 Oct 2026 09:41:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 98A9C68F76; Thu, 22 Oct 2026 11:41:32 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="bXrPmHWQ"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D43F368F6F for ; Thu, 22 Oct 2026 11:41:29 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1792662089; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=YTvD5kvSrXSJ0ekJegsKH/JLo76GyYot4K3y17M3gfQ=; b=bXrPmHWQIELBjZDXJc5DoY2CKkW3MwyDb27N3nldNnV0phVIe80ixSdLS6KwtU64yCJTbx Cc8fTC1Oi6lgAdVmqw6hA5+LuOvzv+KXnmxSTRYrfEzffMkQuazvZ4nnt1hoXabyOm1LnO +89PLn+mosNbTUH19dNS2ahtDrWGt/0= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-649-kCO_-43DPH27sYkI3y9NOQ-1; Thu, 22 Oct 2026 09:41:25 +0000 X-MC-Unique: kCO_-43DPH27sYkI3y9NOQ-1 X-Mimecast-MFC-AGG-ID: kCO_-43DPH27sYkI3y9NOQ_1792662084 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 259491800576; Thu, 22 Oct 2026 09:41:24 +0000 (UTC) Received: from mzamazal-thinkpadp1gen7.redhat.corp (headnet04.pony-001.prod.iad2.dc.redhat.com [10.2.32.116]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 4CED71800643; Thu, 22 Oct 2026 09:41:22 +0000 (UTC) From: Milan Zamazal To: libcamera-devel@lists.libcamera.org Cc: Xander Pronk , Kieran Bingham , Rick ten Wolde , Milan Zamazal , =?utf-8?b?QmFybmFiw6FzIFDFkWN6?= =?utf-8?q?e?= Subject: [PATCH v16 3/6] ipa: simple: Add LSC algorithm Date: Thu, 22 Oct 2026 11:40:45 +0200 Message-ID: <20261022094048.20277-4-mzamazal@redhat.com> In-Reply-To: <20261022094048.20277-1-mzamazal@redhat.com> References: <20261022094048.20277-1-mzamazal@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: 4TQZCBm68deQZR4LX1aKDwwvXP3qkFMfHXJUNVFz7dM_1792662084 X-Mimecast-Originator: redhat.com 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: Xander Pronk The algorithm is based on the common libipa lens shading correction implementation. The grid values obtained from the libipa algorithm are passed to the debayer algorithm as an array and used as an an RGB texture. Notes on the implementation: - The overall idea is to keep things simple, to not make the LSC computation unnecessarily expensive. - LscAlgorithm accepts only quantised types. UQ<2,6> is used, to be converted to float in debayering. - The limit of 100 degrees to consider a temperature change noticeable is arbitrary. Co-developed-by: Rick ten Wolde Signed-off-by: Rick ten Wolde Signed-off-by: Xander Pronk Signed-off-by: Milan Zamazal Tested-by: Barnabás Pőcze Reviewed-by: Kieran Bingham --- .../internal/software_isp/debayer_params.h | 12 +- src/ipa/softisp/algorithms/lsc.cpp | 104 ++++++++++++++++++ src/ipa/softisp/algorithms/lsc.h | 51 +++++++++ src/ipa/softisp/algorithms/meson.build | 1 + src/ipa/softisp/ipa_context.h | 5 + src/ipa/softisp/softisp.cpp | 2 + src/libcamera/software_isp/debayer.cpp | 8 ++ 7 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/ipa/softisp/algorithms/lsc.cpp create mode 100644 src/ipa/softisp/algorithms/lsc.h diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h index 0d9196874..2ae1614b3 100644 --- a/include/libcamera/internal/software_isp/debayer_params.h +++ b/include/libcamera/internal/software_isp/debayer_params.h @@ -36,7 +36,17 @@ struct DebayerParams { static constexpr unsigned int kLscValuesPerCell = 4; using LscLookupTable = std::array; - alignas(4) LscLookupTable lscLut{}; + static constexpr auto identityLscLut = [] { + LscLookupTable lut = {}; + /* lut.fill(64) could be used, but it fails with older gcc versions */ + for (size_t i = 0; kLscValuesPerCell * i < lut.size(); i++) { + lut[i * kLscValuesPerCell + 0] = 64; /* == UQ<2, 6>(1.0f).quantized() */ + lut[i * kLscValuesPerCell + 1] = 64; + lut[i * kLscValuesPerCell + 2] = 64; + } + return lut; + }(); + alignas(4) LscLookupTable lscLut = identityLscLut; uint64_t lscLutVersion = 0; }; diff --git a/src/ipa/softisp/algorithms/lsc.cpp b/src/ipa/softisp/algorithms/lsc.cpp new file mode 100644 index 000000000..8316c7e9b --- /dev/null +++ b/src/ipa/softisp/algorithms/lsc.cpp @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Lens shading correction + */ + +#include "lsc.h" + +#include + +namespace libcamera { + +namespace ipa::softisp::algorithms { + +LOG_DEFINE_CATEGORY(IPASoftIspLsc) + +int Lsc::init(IPAContext &context, const ValueNode &tuningData) +{ + static constexpr unsigned int kGridSize = DebayerParams::kLscGridSize; + + for (unsigned int i = 0; i < kGridSize; i++) + gridPos_.push_back(static_cast(i) / (kGridSize - 1)); + + return lscAlgo_.init(tuningData, context.ctrlMap, + { .keys = { "r", "g", "b" }, + .numHSamples = kGridSize, + .numVSamples = kGridSize, + .sensorSize = context.sensorInfo.activeArea.size() }); +} + +int Lsc::configure(IPAContext &context, + [[maybe_unused]] const IPAConfigInfo &configInfo) +{ + return lscAlgo_.configure(context.activeState.lsc, + context.sensorInfo.analogCrop, + gridPos_, gridPos_); +} + +void Lsc::prepare([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) +{ + unsigned int ct = frameContext.awb.colourTemperature; + constexpr unsigned int minTemperatureChange = 100; + + if (!frameContext.lsc.enabled) { + if (lastAppliedCt_ != 0) { + params->lscLut = DebayerParams::identityLscLut; + params->lscLutVersion++; + lastAppliedCt_ = 0; + + LOG(IPASoftIspLsc, Debug) + << "LSC disabled (version " << params->lscLutVersion << ")"; + } + return; + } + + if (utils::abs_diff(ct, lastAppliedCt_) < minTemperatureChange) + return; + + const auto &set = lscAlgo_.interpolateComponents(ct); + + const auto &red = set.at("r"); + const auto &green = set.at("g"); + const auto &blue = set.at("b"); + + DebayerParams::LscLookupTable &lut = params->lscLut; + constexpr unsigned int gridSize = DebayerParams::kLscGridSize; + for (unsigned int i = 0, j = 0; i < gridSize * gridSize; i++) { + lut[j++] = red[i]; + lut[j++] = green[i]; + lut[j++] = blue[i]; + lut[j++] = 0; /* padding */ + } + params->lscLutVersion++; + + LOG(IPASoftIspLsc, Debug) + << "LSC updated to version " << params->lscLutVersion + << " and temperature " << ct << " (was " << lastAppliedCt_ << ")"; + + lastAppliedCt_ = ct; +} + +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc, + controls); +} + +void Lsc::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const SwIspStats *stats, + ControlList &metadata) +{ + lscAlgo_.process(frameContext.lsc, metadata); +} + +REGISTER_IPA_ALGORITHM(Lsc, "Lsc") + +} /* namespace ipa::softisp::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/softisp/algorithms/lsc.h b/src/ipa/softisp/algorithms/lsc.h new file mode 100644 index 000000000..83ec5aec2 --- /dev/null +++ b/src/ipa/softisp/algorithms/lsc.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Lens shading correction + */ + +#pragma once + +#include + +#include "libipa/fixedpoint.h" +#include "libipa/lsc.h" + +#include "algorithm.h" +#include "ipa_context.h" + +namespace libcamera { + +namespace ipa::softisp::algorithms { + +class Lsc : public Algorithm +{ +public: + Lsc() = default; + ~Lsc() = default; + + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, + const IPAConfigInfo &configInfo) override; + void queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) override; + void prepare(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + DebayerParams *params) override; + void process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const SwIspStats *stats, + ControlList &metadata) override; + +private: + LscAlgorithm> lscAlgo_; + + std::vector gridPos_; + + unsigned int lastAppliedCt_ = 0; +}; + +} /* namespace ipa::softisp::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/softisp/algorithms/meson.build b/src/ipa/softisp/algorithms/meson.build index d240409e5..d98f4b1f4 100644 --- a/src/ipa/softisp/algorithms/meson.build +++ b/src/ipa/softisp/algorithms/meson.build @@ -6,4 +6,5 @@ softisp_ipa_algorithms = files([ 'agc.cpp', 'blc.cpp', 'ccm.cpp', + 'lsc.cpp', ]) diff --git a/src/ipa/softisp/ipa_context.h b/src/ipa/softisp/ipa_context.h index 397e6c99b..00326e6c7 100644 --- a/src/ipa/softisp/ipa_context.h +++ b/src/ipa/softisp/ipa_context.h @@ -22,6 +22,7 @@ #include #include #include +#include "libipa/lsc.h" #include "core_ipa_interface.h" @@ -55,6 +56,8 @@ struct IPAActiveState { std::optional contrast; std::optional saturation; } knobs; + + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -70,6 +73,7 @@ struct IPAFrameContext : public FrameContext { float gamma; std::optional contrast; std::optional saturation; + ipa::lsc::FrameContext lsc; }; struct IPAContext { @@ -86,6 +90,7 @@ struct IPAContext { FCQueue frameContexts; ControlInfoMap::Map ctrlMap; bool ccmEnabled = false; + ipa::lsc::ActiveState lsc; }; } /* namespace ipa::softisp */ diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp index 8acfcd1a0..420fee745 100644 --- a/src/ipa/softisp/softisp.cpp +++ b/src/ipa/softisp/softisp.cpp @@ -164,6 +164,8 @@ int IPASoftIsp::init(const IPASettings &settings, params_->gamma = 1.0 / algorithms::kDefaultGamma; params_->contrastExp = 1.0; params_->gains = { { 1.0, 1.0, 1.0 } }; + params_->lscLutVersion = 0; + params_->lscLut = DebayerParams::identityLscLut; /* combinedMatrix is reset for each frame. */ } diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp index 82cdf1dc2..6ebf7ed1a 100644 --- a/src/libcamera/software_isp/debayer.cpp +++ b/src/libcamera/software_isp/debayer.cpp @@ -66,6 +66,14 @@ namespace libcamera { * \brief Lens shading lookup table */ +/** + * \var DebayerParams::identityLscLut + * \brief Lens shading lookup table of identity mapping + * + * This table can be used to initialise the lens shading lookup table or to + * not apply any real lens shading correction. + */ + /** * \var DebayerParams::lscLutVersion * \brief Incremented on each \a lscLut change