From patchwork Wed Aug 26 18:19:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Milan Zamazal X-Patchwork-Id: 28107 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 738C3C333C for ; Wed, 26 Aug 2026 18:20:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2105868453; Wed, 26 Aug 2026 20:20:21 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.b="HK0mGNSv"; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id DA59D68448 for ; Wed, 26 Aug 2026 20:20:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787768418; 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=vRmHCBvEFemLBl2ydcuaqf37nKGWYIwTmIK2EdVLGb0=; b=HK0mGNSvOviJNbM9TcairLdMz45ctH+4oZO1Bwb6dlPkZg4/eKWMm20OoN+YOp4Ysy7KX9 mOY3Sd/WyAx1JnudoABN8VaIMvSLmNcTCKev3LM4if9NAnmpynoMms9Y/0H+81tpSHUTMZ +CXeZTKsl2a0gkY0CPiH+/awuQYqA04= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-660-LLeaPhR1M3u_vLxBccxjlw-1; Wed, 26 Aug 2026 14:20:14 -0400 X-MC-Unique: LLeaPhR1M3u_vLxBccxjlw-1 X-Mimecast-MFC-AGG-ID: LLeaPhR1M3u_vLxBccxjlw_1787768413 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (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-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 0F2D31955E97; Wed, 26 Aug 2026 18:20:12 +0000 (UTC) Received: from mzamazal-thinkpadp1gen7.redhat.corp (headnet05.pony-001.prod.iad2.dc.redhat.com [10.2.32.117]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 115191800347; Wed, 26 Aug 2026 18:20:08 +0000 (UTC) From: Milan Zamazal To: libcamera-devel@lists.libcamera.org Cc: Xander Pronk , Bryan O'Donoghue , Hans de Goede , Laurent Pinchart , Robert Mader , =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Rick ten Wolde , Milan Zamazal Subject: [PATCH v15 3/6] ipa: simple: Add LSC algorithm Date: Wed, 26 Aug 2026 20:19:50 +0200 Message-ID: <20260826181953.261754-4-mzamazal@redhat.com> In-Reply-To: <20260826181953.261754-1-mzamazal@redhat.com> References: <20260826181953.261754-1-mzamazal@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: qpgtlPIrqWl9B-RXbMxANeo2I4LCMT5acNPz0oVlQNc_1787768413 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 --- .../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..9893b4fa9 --- /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.activeAreaSize }); +} + +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 2eed32f88..e9c3eef0e 100644 --- a/src/ipa/softisp/ipa_context.h +++ b/src/ipa/softisp/ipa_context.h @@ -19,6 +19,7 @@ #include #include #include +#include "libipa/lsc.h" #include "core_ipa_interface.h" @@ -61,6 +62,8 @@ struct IPAActiveState { std::optional contrast; std::optional saturation; } knobs; + + ipa::lsc::ActiveState lsc; }; struct IPAFrameContext : public FrameContext { @@ -75,6 +78,7 @@ struct IPAFrameContext : public FrameContext { float gamma; std::optional contrast; std::optional saturation; + ipa::lsc::FrameContext lsc; }; struct IPAContext { @@ -89,6 +93,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 aec04c528..41d3e23d8 100644 --- a/src/ipa/softisp/softisp.cpp +++ b/src/ipa/softisp/softisp.cpp @@ -163,6 +163,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