From patchwork Thu Aug 6 15:19:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Plowman X-Patchwork-Id: 27665 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 3031CC330A for ; Thu, 6 Aug 2026 15:27:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8901968186; Thu, 6 Aug 2026 17:27:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=raspberrypi.com header.i=@raspberrypi.com header.b="dyPSEAEA"; dkim-atps=neutral Received: from mail-wm1-x336.google.com (mail-wm1-x336.google.com [IPv6:2a00:1450:4864:20::336]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4BDDA68169 for ; Thu, 6 Aug 2026 17:27:06 +0200 (CEST) Received: by mail-wm1-x336.google.com with SMTP id 5b1f17b1804b1-495590dde14so26853095e9.0 for ; Thu, 06 Aug 2026 08:27:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=raspberrypi.com; s=google; t=1786030026; x=1786634826; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=LaAYL+469tyiBvmLl9L8gVVPj1Cx8ia1LyQR7+pHpKg=; b=dyPSEAEAT0gZdFSl/AOxfX8V0WgfgSdJXWYV5Tv6y/Ir+Kt/mhHwV9e7bthr59KKNZ KACwFU+lvkiYpDEIrrGk1WZmUPqAo+A4nduCNdTs984zUcDJ/2hqed6c7f1lxCFXofww 8eIF6rOBtwPtD20WAPaffeBtaQ9dxT8QybvPPUbhUNIozazl92yB9xNtDutj7HQaUceU 8/yRdHljQHaSz8I2+vCEurNvqsLouHXRT8u3Ji/NlwW2Kds/bRASsmeKnp2W+rSbauvN vYVOmRoihjf9fSzsfs/2ukI73ehMmh9lKOCnQJZ0Y8qLAOyzArri7uIdIlw5z7iDP9iS 8/hA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786030026; x=1786634826; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=LaAYL+469tyiBvmLl9L8gVVPj1Cx8ia1LyQR7+pHpKg=; b=aDTy5rjd33K5jtbwrrv7zsjnv1OH8DeVPQp9SNA79fzsjdXIhU26BxYkhlRQVrxmyE qAnoHFqMUJbDPsxdt8vS3o2spenvt94MkeQTD/VhBJMZv9+ZI4TKGpWQRV32ADdRuOSF qtuhGno9AhzRKV9qZbuFJh9UTM5dbXEin6CKsPLvIIbfckMKL9FwBxy0JuFiI2Lcu5sm usmhcVB/as2/fBY6b6ZTPtelLs3QnwE5c1yaMixOYnG3145hPVqqAk9iUSkt+0hUw/A+ 7Mjn47d2ARy8eCF8lGvE1jGXNGh1U5FgUR0MXAat28zIZFGtTMAUvwbSf2ku5aMvcDfj hBsQ== X-Gm-Message-State: AOJu0YzK0pe1ubkexG4km4NriSMdgL3k/CiW3vrKCHvpF0MeWP5AmtZ8 fkkVLbBlpM0Cci4W5e2mBMKlX7kt0C4k7PXtlNzCYgtjlaiv3RBWjxLvx7AnUYNlMUN+h9mfZN4 kl9T9 X-Gm-Gg: AR+sD137mQY3/c4qwglbT9e1b/fZYkqUs85ytWGyysR3Ak72H1b/lHGsarxP2U6NxNQ FmoMCBcaD3hdkrNsoWgwGr2qaFIBmVj7RtCDbfip6UoCxhnfS2SOBz3ZZ/Y5oRfvfscUlyfJK55 wjGjuHXpiea7VdcGWLnvAFzGtZyExUwX4y2AwSHg2LDOGCjkRNYCxc7J5h5WiQXtSW7QFiUVbMS /UKpXWnLCJlqq3+X7ZYl6oN8W4xzb7Oi6bQCzzxky+qQfcD5UkSxuF255uNf7XePJ3gvjN32gp1 lwVcv/UqgVsGXQ/kQIt2Gvlu6cRtqmNdK65QA5/jy9bA60t7vYcd9kqM4FpX7riJCc2vKG0x+kk oUY913Y0DAxXRK2i0vbVX48oQHhHF14qMgRDFDs7QpqRo9OXa61Hrm/LpGCrQI38zcjVI2GZhNT yoHe45XplA87oUgxB3ceNqmNkjw05JEzd3kFsKAl2kJINd+IyK3fNbP4u876yvRtUF5F/Cs5RFF X6SPG6VnhDdEST6bCW1zOwktLsKibdm/f7fYMmdKkTt7xiPslZnMeyz9d/u77YyznQISI6QFjLV Rqfa X-Received: by 2002:a05:600c:474a:b0:496:c361:fb96 with SMTP id 5b1f17b1804b1-4994e6db5aemr188185655e9.0.1786030025678; Thu, 06 Aug 2026 08:27:05 -0700 (PDT) Received: from davidp-pi5.pitowers.org ([2a00:1098:3142:1f:4706:89ee:c47e:1087]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4995428f294sm70769235e9.15.2026.08.06.08.27.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 06 Aug 2026 08:27:05 -0700 (PDT) From: David Plowman To: libcamera-devel@lists.libcamera.org Cc: David Plowman Subject: [PATCH v2 2/4] ipa: rpi: Allow black levels to vary with analogue gain Date: Thu, 6 Aug 2026 16:19:42 +0100 Message-ID: <20260806152701.4179-3-david.plowman@raspberrypi.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260806152701.4179-1-david.plowman@raspberrypi.com> References: <20260806152701.4179-1-david.plowman@raspberrypi.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" Optionally, a PWL (piecewise linear) function can be specified as the black level instead of a constant value. This PWL is evaluated at the current analogue gain for each frame. Tuning files will also accept "black_level_func" as a shorthand for all three channels. Signed-off-by: David Plowman --- src/ipa/rpi/controller/rpi/black_level.cpp | 45 ++++++++++++++++++++-- src/ipa/rpi/controller/rpi/black_level.h | 7 ++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/ipa/rpi/controller/rpi/black_level.cpp b/src/ipa/rpi/controller/rpi/black_level.cpp index 42ea15050..92a258048 100644 --- a/src/ipa/rpi/controller/rpi/black_level.cpp +++ b/src/ipa/rpi/controller/rpi/black_level.cpp @@ -41,12 +41,41 @@ int BlackLevel::read(const libcamera::ValueNode ¶ms) << " Read black levels red " << blackLevelR_ << " green " << blackLevelG_ << " blue " << blackLevelB_; + + /* Allow "black_level_func" as a shorthand for all 3 colours. */ + libcamera::ipa::Pwl blackLevelFunc; + blackLevelFunc = params["black_level_func"].get(ipa::Pwl{}); + blackLevelFuncR_ = params["black_level_func_r"].get(blackLevelFunc); + blackLevelFuncG_ = params["black_level_func_g"].get(blackLevelFunc); + blackLevelFuncB_ = params["black_level_func_b"].get(blackLevelFunc); + + /* Warn the user if they've defined constant black levels but which are unused. */ + if (!blackLevelFuncR_.empty() && !blackLevelFuncG_.empty() && + !blackLevelFuncB_.empty() && params.contains("black_level")) + LOG(RPiBlackLevel, Warning) << "\"black_level\" specified but not used"; + + if (!blackLevelFuncR_.empty() && params.contains("black_level_r")) + LOG(RPiBlackLevel, Warning) << "\"black_level_r\" specified but not used"; + if (!blackLevelFuncG_.empty() && params.contains("black_level_g")) + LOG(RPiBlackLevel, Warning) << "\"black_level_g\" specified but not used"; + if (!blackLevelFuncB_.empty() && params.contains("black_level_b")) + LOG(RPiBlackLevel, Warning) << "\"black_level_b\" specified but not used"; + return 0; } void BlackLevel::initialValues(uint16_t &blackLevelR, uint16_t &blackLevelG, uint16_t &blackLevelB) { + if (!blackLevelFuncR_.empty()) + blackLevelR_ = blackLevelFuncR_.eval(blackLevelFuncR_.domain().clamp(1.0)); + + if (!blackLevelFuncG_.empty()) + blackLevelG_ = blackLevelFuncG_.eval(blackLevelFuncG_.domain().clamp(1.0)); + + if (!blackLevelFuncB_.empty()) + blackLevelB_ = blackLevelFuncB_.eval(blackLevelFuncB_.domain().clamp(1.0)); + blackLevelR = blackLevelR_; blackLevelG = blackLevelG_; blackLevelB = blackLevelB_; @@ -54,10 +83,18 @@ void BlackLevel::initialValues(uint16_t &blackLevelR, uint16_t &blackLevelG, void BlackLevel::prepare(Metadata *imageMetadata) { - /* - * Possibly we should think about doing this in a switchMode or - * something? - */ + DeviceStatus deviceStatus; + if (!imageMetadata->get("device.status", deviceStatus)) { + if (!blackLevelFuncR_.empty()) + blackLevelR_ = blackLevelFuncR_.eval(blackLevelFuncR_.domain().clamp(deviceStatus.analogueGain)); + + if (!blackLevelFuncG_.empty()) + blackLevelG_ = blackLevelFuncG_.eval(blackLevelFuncG_.domain().clamp(deviceStatus.analogueGain)); + + if (!blackLevelFuncB_.empty()) + blackLevelB_ = blackLevelFuncB_.eval(blackLevelFuncB_.domain().clamp(deviceStatus.analogueGain)); + } + struct BlackLevelStatus status; status.blackLevelR = blackLevelR_; status.blackLevelG = blackLevelG_; diff --git a/src/ipa/rpi/controller/rpi/black_level.h b/src/ipa/rpi/controller/rpi/black_level.h index dbf29b282..34bb81548 100644 --- a/src/ipa/rpi/controller/rpi/black_level.h +++ b/src/ipa/rpi/controller/rpi/black_level.h @@ -6,6 +6,8 @@ */ #pragma once +#include + #include "../black_level_algorithm.h" #include "../black_level_status.h" @@ -27,6 +29,11 @@ private: double blackLevelR_; double blackLevelG_; double blackLevelB_; + + /* Black levels can vary with analogue gain instead of being constant. */ + libcamera::ipa::Pwl blackLevelFuncR_; + libcamera::ipa::Pwl blackLevelFuncG_; + libcamera::ipa::Pwl blackLevelFuncB_; }; } /* namespace RPiController */