From patchwork Fri Jan 16 12:16:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 25824 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 C0B73C3226 for ; Fri, 16 Jan 2026 12:17:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F177861FA3; Fri, 16 Jan 2026 13:17:04 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="GxisCiqR"; 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 264F6615B2 for ; Fri, 16 Jan 2026 13:17:04 +0100 (CET) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:cc1c:cb5b:bc84:204]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id C3CE54B3; Fri, 16 Jan 2026 13:16:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1768565795; bh=0dsJCDBn3y6kCF1ZOdFVXYrXMKd33tDo8KbNS4P40jg=; h=From:To:Cc:Subject:Date:From; b=GxisCiqRAX8QnVyvc+LWW1NAUOXNncIWeOB77L5ox5h/7tsJX8H7PHAhWZN723gNW +s31+dgruEupcYPV8ghJZ+z94XDyyymCyKQaufFbpSZKvMvL6Lcd7PzVofDfU1CTsT VcYPuDWCqKPQXRqhBDh2l7meEu8cbhBWYEGnKFfA= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v1] ipa: rkisp1: cproc: Fix brightness and contrast value handling Date: Fri, 16 Jan 2026 13:16:14 +0100 Message-ID: <20260116121658.215712-1-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.51.0 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" By reverse engineering it was discovered that the transfer curve applied by the CPROC module is Yout = Yin * contrast + brightness/2. However the most common expectation on contrast is to not change the middle gray of an image. So the expected curve is: Yout = (Yin - 0.5) * contrast + 0.5 + brightness Map the user expectation to the hardware by changing the brightness value written to the hardware. This also includes multiplying the requested brightness value by two. Note: The limits of the brightness control are left as is even though they can not be reached without changing the contrast. But limiting them to [0.5, 0.5] would cut off parts of the combined control space of contrast + brightness. Signed-off-by: Stefan Klug --- This patch needs to be applied on top of the quantization series: https://patchwork.libcamera.org/project/libcamera/list/?series=5708 --- src/ipa/rkisp1/algorithms/cproc.cpp | 38 +++++++++++++++++++++++------ src/ipa/rkisp1/ipa_context.h | 1 + 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp index 7484e4780094..ec2b264a79b9 100644 --- a/src/ipa/rkisp1/algorithms/cproc.cpp +++ b/src/ipa/rkisp1/algorithms/cproc.cpp @@ -100,13 +100,9 @@ void ColorProcessing::queueRequest(IPAContext &context, const auto &brightness = controls.get(controls::Brightness); if (brightness) { - BrightnessQ value = *brightness; - if (cproc.brightness != value) { - cproc.brightness = value; - update = true; - } - - LOG(RkISP1CProc, Debug) << "Set brightness to " << value; + cproc.requestedBrightness = *brightness; + LOG(RkISP1CProc, Debug) << "Set brightness to " << *brightness; + update = true; } const auto &contrast = controls.get(controls::Contrast); @@ -120,6 +116,24 @@ void ColorProcessing::queueRequest(IPAContext &context, LOG(RkISP1CProc, Debug) << "Set contrast to " << value; } + /* + * The CPROC module applies the transfer function + * + * Yout = Yin * contrast + brightness/2 + * + * Most users expect that changing contrast doesn't change the middle + * gray and that the brightness value is normalized to one. So they + * expect a transfer function of + * + * Yout = (Yin - 0.5) * contrast + 0.5 + brightness + * + * To implement that with the given hardware we need to correct the + * brightness value. + */ + if (update) { + cproc.brightness = (1 - cproc.contrast.value()) + 2 * cproc.requestedBrightness; + } + const auto &hue = controls.get(controls::Hue); if (hue) { /* Scale the Hue from ]-90, +90] */ @@ -179,7 +193,15 @@ void ColorProcessing::process([[maybe_unused]] IPAContext &context, [[maybe_unused]] const rkisp1_stat_buffer *stats, ControlList &metadata) { - metadata.set(controls::Brightness, frameContext.cproc.brightness.value()); + /* + * Report the actually applied brightness value. See queueRequest() for + * more details. + */ + float actualBrightness = (frameContext.cproc.brightness.value() - + (1 - frameContext.cproc.contrast.value())) / + 2; + + metadata.set(controls::Brightness, actualBrightness); metadata.set(controls::Contrast, frameContext.cproc.contrast.value()); metadata.set(controls::Hue, frameContext.cproc.hue.value() * kHueScale); metadata.set(controls::Saturation, frameContext.cproc.saturation.value()); diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 80b035044cda..309567858b64 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -122,6 +122,7 @@ struct IPAActiveState { } ccm; struct { + float requestedBrightness; BrightnessQ brightness; ContrastQ contrast; HueQ hue;