From patchwork Fri Jul 3 12:25:20 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 27173 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 63083C328C for ; Fri, 3 Jul 2026 12:26:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E5C4A65FED; Fri, 3 Jul 2026 14:26:51 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cJkcWQTG"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 051D665FE2 for ; Fri, 3 Jul 2026 14:26:50 +0200 (CEST) Received: from neptunite.hamster-moth.ts.net (unknown [IPv6:2404:7a81:160:2100:a2cc:2f45:3bd7:2589]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 262261121; Fri, 3 Jul 2026 14:26:00 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783081564; bh=F6zaFA2WMPuL8yNraGr/Kka8q5Lh7u+ysuifoo/7AxA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cJkcWQTGeOw0mbR6ztFR7ES/zg458iuQrNjM+Tp6ZJdpuG8YpO5ltkisFbTAwOy9D zUG0kFrkdzH4IKd6p1YzJNVRcC9cUlEZ2tO3uB3NC9EjNKAp8OCQoKTEdfuzX8pJkT I4kzZ3RsfXlJjE2qyjjoWw7grhvzkLxEdxhoM0qg= From: Paul Elder To: laurent.pinchart@ideasonboard.com Cc: Paul Elder , michael.riesch@collabora.com, xuhf@rock-chips.com, stefan.klug@ideasonboard.com, kieran.bingham@ideasonboard.com, dan.scally@ideasonboard.com, jacopo.mondi@ideasonboard.com, nicolas.dufresne@collabora.com, libcamera-devel@lists.libcamera.org Subject: [RFC PATCH 14/19] ipa: rkisp2: algo: lsc: Implement lens shading correction Date: Fri, 3 Jul 2026 21:25:20 +0900 Message-ID: <20260703122543.1991189-15-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260703122543.1991189-1-paul.elder@ideasonboard.com> References: <20260703122543.1991189-1-paul.elder@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" Implement a lens shading correction algorithm for the rkisp2 IPA. It uses the libipa lens shading correction. Signed-off-by: Paul Elder --- src/ipa/rkisp2/algorithms/lsc.cpp | 263 ++++++++++++++++++++++++++ src/ipa/rkisp2/algorithms/lsc.h | 71 +++++++ src/ipa/rkisp2/algorithms/meson.build | 1 + 3 files changed, 335 insertions(+) create mode 100644 src/ipa/rkisp2/algorithms/lsc.cpp create mode 100644 src/ipa/rkisp2/algorithms/lsc.h diff --git a/src/ipa/rkisp2/algorithms/lsc.cpp b/src/ipa/rkisp2/algorithms/lsc.cpp new file mode 100644 index 000000000000..dd93583dffdc --- /dev/null +++ b/src/ipa/rkisp2/algorithms/lsc.cpp @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RkISP2 Lens Shading Correction control + */ + +#include "lsc.h" + +#include +#include +#include + +#include +#include + +/** + * \file lsc.h + */ + +namespace libcamera { + +namespace ipa::rkisp2::algorithms { + +/** + * \class LensShadingCorrection + * \brief RkISP2 Lens Shading Correction control + */ + +LOG_DEFINE_CATEGORY(RkISP2Lsc) + +namespace { + +constexpr int kColourTemperatureQuantization = 10; + +unsigned int quantize(unsigned int value, unsigned int step) +{ + return std::lround(value / static_cast(step)) * step; +} + +} /* namespace */ + +LensShadingCorrection::LensShadingCorrection() + : lastAppliedCt_(0), lastAppliedQuantizedCt_(0) +{ +} + +std::vector LensShadingCorrection::parseSizes(const ValueNode &tuningData, + const char *prop) +{ + std::vector sizes = + tuningData[prop].get>().value_or(utils::defopt); + /* Nobody cares about 8x8 mirrored mode; we'll just use 16x16 mode */ + if (sizes.size() != RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX) { + LOG(RkISP2Lsc, Error) + << "Invalid '" << prop << "' values: expected " + << RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX + << " elements, got " << sizes.size(); + return {}; + } + + /* + * The sum of all elements must be 1 to 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()). + * + * If we were in 8x8 mode then we'd have to mirror the quadrants like + * in rkisp1, but in 16x16 mode we get to configure the entire table. + * Since 8x8 table support is a todo, we only need to handle the 16x16 + * case here thus the sum should be 1. + * + * \todo Support 8x8 mode? + */ + double sum = std::accumulate(sizes.begin(), sizes.end(), 0.0); + if (sum < 0.95 || sum > 1.05) { + LOG(RkISP2Lsc, Error) + << "Invalid '" << prop << "' values: sum of the elements" + << " should be 1.0, got " << sum; + return {}; + } + + return sizes; +} + +std::vector LensShadingCorrection::sizesToPositions(Span sizes) +{ + std::vector positions(sizes.size() + 1); + + positions[0] = 0.0; + for (size_t i = 1; i < positions.size(); i++) + positions[i] = positions[i - 1] + sizes[i - 1]; + + return positions; +} + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int LensShadingCorrection::init(IPAContext &context, + const ValueNode &tuningData) +{ + xSize_ = parseSizes(tuningData, "x-size"); + ySize_ = parseSizes(tuningData, "y-size"); + + if (xSize_.empty() || ySize_.empty()) + return -EINVAL; + + xPos_ = sizesToPositions(xSize_); + yPos_ = sizesToPositions(ySize_); + + return lscAlgo_.init(tuningData, context.ctrlMap, { + .keys = { "r", "gr", "gb", "b" }, + .numHCells = RKISP2_ISP_LSC_SAMPLES_MAX, + .numVCells = RKISP2_ISP_LSC_SAMPLES_MAX, + .sensorSize = context.sensorInfo.activeAreaSize + }); +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int LensShadingCorrection::configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) +{ + const Size &size = context.configuration.sensor.size; + Size totalSize{}; + + /* Calculate gradients. */ + for (unsigned int i = 0; i < RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX; ++i) { + xSizes_[i] = xSize_[i] * size.width; + ySizes_[i] = ySize_[i] * size.height; + + /* + * To prevent unexpected behavior of the ISP, the sum of + * x_sizes and y_sizes items shall be equal to + * respectively size.width and size.height. Enforce it by + * computing the last tables value to avoid + * rounding-induced errors. + */ + if (i == RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX - 1) { + xSizes_[i] = size.width - totalSize.width; + ySizes_[i] = size.height - totalSize.height; + } + + totalSize.width += xSizes_[i]; + totalSize.height += ySizes_[i]; + + xGrad_[i] = std::round(32768 / xSizes_[i]); + yGrad_[i] = std::round(32768 / ySizes_[i]); + } + + return lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop, + xPos_, yPos_); +} + +void LensShadingCorrection::setParameters(rkisp2_params_lsc &config) +{ + memcpy(config.x_grads, xGrad_, sizeof(config.x_grads)); + memcpy(config.y_grads, yGrad_, sizeof(config.y_grads)); + memcpy(config.x_sizes, xSizes_, sizeof(config.x_sizes)); + memcpy(config.y_sizes, ySizes_, sizeof(config.y_sizes)); +} + +void LensShadingCorrection::copyTable(rkisp2_params_lsc &config, + const ipa::lsc::Components &set) +{ + const auto &r = set.at("r"); + std::copy(r.begin(), r.end(), &config.r_data_tbl[0][0][0]); + const auto &gr = set.at("gr"); + std::copy(gr.begin(), gr.end(), &config.gr_data_tbl[0][0][0]); + const auto &gb = set.at("gb"); + std::copy(gb.begin(), gb.end(), &config.gb_data_tbl[0][0][0]); + const auto &b = set.at("b"); + std::copy(b.begin(), b.end(), &config.b_data_tbl[0][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, + RkISP2Params *params) +{ + uint32_t ct = frameContext.awb.temperatureK; + 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; + + /* + * \todo Should add support for the lsc table swapping functionality? + * Or maybe we don't need it because the lsc doesn't change very + * frequently. Just use the 0th table for now. + */ + config->window_mode = RKISP2_ISP_LSC_CONFIG_16X16; + config->write_table[0] = 1; + config->write_table[1] = 0; + config->active_table = 0; + config->set_active_table_when = RKISP2_ISP_LSC_SET_ACTIVE_TABLE_AFTER; + + setParameters(*config); + + const auto &set = lscAlgo_.interpolateComponents(quantizedCt); + copyTable(*config, set); + + lastAppliedCt_ = ct; + lastAppliedQuantizedCt_ = quantizedCt; + + LOG(RkISP2Lsc, 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 rkisp2_stats_buffer *stats, + ControlList &metadata) +{ + lscAlgo_.process(frameContext.lsc, metadata); +} + +REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection") + +} /* namespace ipa::rkisp2::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rkisp2/algorithms/lsc.h b/src/ipa/rkisp2/algorithms/lsc.h new file mode 100644 index 000000000000..78cbb094ec64 --- /dev/null +++ b/src/ipa/rkisp2/algorithms/lsc.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RkISP2 Lens Shading Correction algorithm + */ + +#pragma once + +#include + +#include + +#include "libcamera/internal/value_node.h" + +#include "libipa/fixedpoint.h" +#include "libipa/lsc.h" + +#include "algorithm.h" +#include "ipa_context.h" +#include "params.h" + +namespace libcamera { + +namespace ipa::rkisp2::algorithms { + +class LensShadingCorrection : public Algorithm +{ +public: + LensShadingCorrection(); + ~LensShadingCorrection() = default; + + 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, + RkISP2Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const rkisp2_stats_buffer *stats, + ControlList &metadata) override; + +private: + std::vector parseSizes(const ValueNode &tuningData, + const char *prop); + std::vector sizesToPositions(Span sizes); + + void setParameters(rkisp2_params_lsc &config); + void copyTable(rkisp2_params_lsc &config, + const ipa::lsc::Components &set0); + + std::vector xSize_; + std::vector ySize_; + uint16_t xGrad_[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX]; + uint16_t yGrad_[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX]; + uint16_t xSizes_[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX]; + uint16_t ySizes_[RKISP2_ISP_LSC_SECTORS_TBL_SIZE_MAX]; + std::vector xPos_; + std::vector yPos_; + + unsigned int lastAppliedCt_; + unsigned int lastAppliedQuantizedCt_; + + LscAlgorithm> lscAlgo_; +}; + +} /* namespace ipa::rkisp2::algorithms */ +} /* namespace libcamera */ diff --git a/src/ipa/rkisp2/algorithms/meson.build b/src/ipa/rkisp2/algorithms/meson.build index 3d73b0a99320..bcc947fabdb4 100644 --- a/src/ipa/rkisp2/algorithms/meson.build +++ b/src/ipa/rkisp2/algorithms/meson.build @@ -7,5 +7,6 @@ rkisp2_ipa_algorithms = files([ 'ccm.cpp', 'csm.cpp', 'goc.cpp', + 'lsc.cpp', ])